Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7abb216
[CIR] Upstream overflow builtins
adams381 Nov 5, 2025
d032089
Update clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
adams381 Nov 18, 2025
7d73c08
Update clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
adams381 Nov 18, 2025
848fbe7
Update clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
adams381 Nov 18, 2025
d42d928
Update clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
adams381 Nov 18, 2025
e2b3629
Update clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
adams381 Nov 19, 2025
f77a61d
Update clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
adams381 Nov 19, 2025
781e7bb
Update clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
adams381 Nov 19, 2025
3b01b5a
[CIR] Remove createBinOpOverflowOp helper function
adams381 Nov 19, 2025
05600b8
[CIR] Fix style and deprecated API in overflow builtins lowering
adams381 Nov 19, 2025
5c00c3d
[CIR] Fix style and remove helper function in overflow builtins
adams381 Nov 19, 2025
afa0bda
Update clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
adams381 Nov 19, 2025
28d8de4
Update clang/test/CIR/CodeGen/builtins-overflow.cpp
adams381 Nov 19, 2025
1767a97
Update clang/include/clang/CIR/Dialect/IR/CIROps.td
adams381 Nov 19, 2025
8e7c9a4
Update clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
adams381 Nov 19, 2025
03f4133
Update clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
adams381 Nov 19, 2025
7efb03f
Update clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
adams381 Nov 19, 2025
1c61733
Update clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
adams381 Nov 19, 2025
b18aedf
Update clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
adams381 Nov 19, 2025
ebcb509
Update clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
adams381 Nov 19, 2025
27a3db7
Update clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
adams381 Nov 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1628,6 +1628,82 @@ def CIR_CmpOp : CIR_Op<"cmp", [Pure, SameTypeOperands]> {
let isLLVMLoweringRecursive = true;
}

//===----------------------------------------------------------------------===//
// BinOpOverflowOp
//===----------------------------------------------------------------------===//

def CIR_BinOpOverflowKind : CIR_I32EnumAttr<
"BinOpOverflowKind", "checked binary arithmetic operation kind", [
I32EnumAttrCase<"Add", 0, "add">,
I32EnumAttrCase<"Sub", 1, "sub">,
I32EnumAttrCase<"Mul", 2, "mul">
]>;

def CIR_BinOpOverflowOp : CIR_Op<"binop.overflow", [Pure, SameTypeOperands]> {
let summary = "Perform binary integral arithmetic with overflow checking";
let description = [{
`cir.binop.overflow` performs binary arithmetic operations with overflow
checking on integral operands.

The `kind` argument specifies the kind of arithmetic operation to perform.
It can be either `add`, `sub`, or `mul`. The `lhs` and `rhs` arguments
specify the input operands of the arithmetic operation. The types of `lhs`
and `rhs` must be the same.

`cir.binop.overflow` produces two SSA values. `result` is the result of the
arithmetic operation truncated to its specified type. `overflow` is a
boolean value indicating whether overflow happens during the operation.

The exact semantic of this operation is as follows:

- `lhs` and `rhs` are promoted to an imaginary integral type that has
infinite precision.
- The arithmetic operation is performed on the promoted operands.
- The infinite-precision result is truncated to the type of `result`. The
truncated result is assigned to `result`.
- If the truncated result is equal to the un-truncated result, `overflow`
is assigned to false. Otherwise, `overflow` is assigned to true.
}];

let arguments = (ins
CIR_BinOpOverflowKind:$kind,
CIR_IntType:$lhs,
CIR_IntType:$rhs
);

let results = (outs CIR_IntType:$result, CIR_BoolType:$overflow);

let assemblyFormat = [{
`(` $kind `,` $lhs `,` $rhs `)` `:` qualified(type($lhs)) `->`
`(` qualified(type($result)) `,` qualified(type($overflow)) `)`
attr-dict
}];

let builders = [
OpBuilder<(ins "cir::IntType":$resultTy,
"cir::BinOpOverflowKind":$kind,
"mlir::Value":$lhs,
"mlir::Value":$rhs), [{
auto overflowTy = cir::BoolType::get($_builder.getContext());
build($_builder, $_state, resultTy, overflowTy, kind, lhs, rhs);
}]>
];

let extraLLVMLoweringPatternDecl = [{
static std::string getLLVMIntrinName(cir::BinOpOverflowKind opKind,
bool isSigned, unsigned width);

struct EncompassedTypeInfo {
bool sign;
unsigned width;
};

static EncompassedTypeInfo computeEncompassedTypeWidth(cir::IntType operandTy,
cir::IntType resultTy);
}];
}


//===----------------------------------------------------------------------===//
// BinOp
//===----------------------------------------------------------------------===//
Expand Down
187 changes: 187 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,45 @@ static RValue emitBuiltinBitOp(CIRGenFunction &cgf, const CallExpr *e,
return RValue::get(result);
}

namespace {
struct WidthAndSignedness {
unsigned width;
bool isSigned;
};
} // namespace

static WidthAndSignedness
getIntegerWidthAndSignedness(const clang::ASTContext &astContext,
const clang::QualType type) {
assert(type->isIntegerType() && "Given type is not an integer.");
unsigned width = type->isBooleanType() ? 1
: type->isBitIntType() ? astContext.getIntWidth(type)
: astContext.getTypeInfo(type).Width;
bool isSigned = type->isSignedIntegerType();
return {width, isSigned};
}

// Given one or more integer types, this function produces an integer type that
// encompasses them: any value in one of the given types could be expressed in
// the encompassing type.
static struct WidthAndSignedness
EncompassingIntegerType(ArrayRef<struct WidthAndSignedness> types) {
assert(types.size() > 0 && "Empty list of types.");

// If any of the given types is signed, we must return a signed type.
bool isSigned = llvm::any_of(types, [](const auto &t) { return t.isSigned; });

// The encompassing type must have a width greater than or equal to the width
// of the specified types. Additionally, if the encompassing type is signed,
// its width must be strictly greater than the width of any unsigned types
// given.
unsigned width = 0;
for (const auto &type : types)
width = std::max(width, type.width + (isSigned && !type.isSigned));

return {width, isSigned};
}

RValue CIRGenFunction::emitRotate(const CallExpr *e, bool isRotateLeft) {
mlir::Value input = emitScalarExpr(e->getArg(0));
mlir::Value amount = emitScalarExpr(e->getArg(1));
Expand Down Expand Up @@ -491,6 +530,154 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
cir::PrefetchOp::create(builder, loc, address, locality, isWrite);
return RValue::get(nullptr);
}
case Builtin::BI__builtin_add_overflow:
case Builtin::BI__builtin_sub_overflow:
case Builtin::BI__builtin_mul_overflow: {
const clang::Expr *LeftArg = e->getArg(0);
const clang::Expr *RightArg = e->getArg(1);
const clang::Expr *ResultArg = e->getArg(2);

clang::QualType ResultQTy =
ResultArg->getType()->castAs<clang::PointerType>()->getPointeeType();

WidthAndSignedness LeftInfo =
getIntegerWidthAndSignedness(cgm.getASTContext(), LeftArg->getType());
WidthAndSignedness RightInfo =
getIntegerWidthAndSignedness(cgm.getASTContext(), RightArg->getType());
WidthAndSignedness ResultInfo =
getIntegerWidthAndSignedness(cgm.getASTContext(), ResultQTy);

// Note we compute the encompassing type with the consideration to the
// result type, so later in LLVM lowering we don't get redundant integral
// extension casts.
WidthAndSignedness EncompassingInfo =
EncompassingIntegerType({LeftInfo, RightInfo, ResultInfo});

auto EncompassingCIRTy = cir::IntType::get(
&getMLIRContext(), EncompassingInfo.width, EncompassingInfo.isSigned);
auto ResultCIRTy = mlir::cast<cir::IntType>(cgm.convertType(ResultQTy));

mlir::Value Left = emitScalarExpr(LeftArg);
mlir::Value Right = emitScalarExpr(RightArg);
Address ResultPtr = emitPointerWithAlignment(ResultArg);

// Extend each operand to the encompassing type, if necessary.
if (Left.getType() != EncompassingCIRTy)
Left =
builder.createCast(cir::CastKind::integral, Left, EncompassingCIRTy);
if (Right.getType() != EncompassingCIRTy)
Right =
builder.createCast(cir::CastKind::integral, Right, EncompassingCIRTy);

// Perform the operation on the extended values.
cir::BinOpOverflowKind OpKind;
switch (builtinID) {
default:
llvm_unreachable("Unknown overflow builtin id.");
case Builtin::BI__builtin_add_overflow:
OpKind = cir::BinOpOverflowKind::Add;
break;
case Builtin::BI__builtin_sub_overflow:
OpKind = cir::BinOpOverflowKind::Sub;
break;
case Builtin::BI__builtin_mul_overflow:
OpKind = cir::BinOpOverflowKind::Mul;
break;
}

auto Loc = getLoc(e->getSourceRange());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto Loc = getLoc(e->getSourceRange());
mlir::Location loc = getLoc(e->getSourceRange());

We don't use auto unless the type is obvious from the code (such as the result of mlir::cast or T::get) or unhelpfully verbose (such as iterators).

https://llvm.org/docs/CodingStandards.html#use-auto-type-deduction-to-make-code-more-readable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be addressed.

auto arithOp =
cir::BinOpOverflowOp::create(builder, Loc, ResultCIRTy, OpKind, Left, Right);

// Here is a slight difference from the original clang CodeGen:
// - In the original clang CodeGen, the checked arithmetic result is
// first computed as a value of the encompassing type, and then it is
// truncated to the actual result type with a second overflow checking.
// - In CIRGen, the checked arithmetic operation directly produce the
// checked arithmetic result in its expected type.
//
// So we don't need a truncation and a second overflow checking here.

// Finally, store the result using the pointer.
bool isVolatile =
ResultArg->getType()->getPointeeType().isVolatileQualified();
builder.createStore(Loc, emitToMemory(ArithOp.getResult(), ResultQTy),
ResultPtr, isVolatile);

return RValue::get(ArithOp.getOverflow());
}

case Builtin::BI__builtin_uadd_overflow:
case Builtin::BI__builtin_uaddl_overflow:
case Builtin::BI__builtin_uaddll_overflow:
case Builtin::BI__builtin_usub_overflow:
case Builtin::BI__builtin_usubl_overflow:
case Builtin::BI__builtin_usubll_overflow:
case Builtin::BI__builtin_umul_overflow:
case Builtin::BI__builtin_umull_overflow:
case Builtin::BI__builtin_umulll_overflow:
case Builtin::BI__builtin_sadd_overflow:
case Builtin::BI__builtin_saddl_overflow:
case Builtin::BI__builtin_saddll_overflow:
case Builtin::BI__builtin_ssub_overflow:
case Builtin::BI__builtin_ssubl_overflow:
case Builtin::BI__builtin_ssubll_overflow:
case Builtin::BI__builtin_smul_overflow:
case Builtin::BI__builtin_smull_overflow:
case Builtin::BI__builtin_smulll_overflow: {
// Scalarize our inputs.
mlir::Value X = emitScalarExpr(e->getArg(0));
mlir::Value Y = emitScalarExpr(e->getArg(1));

const clang::Expr *ResultArg = e->getArg(2);
Address ResultPtr = emitPointerWithAlignment(ResultArg);

// Decide which of the arithmetic operation we are lowering to:
cir::BinOpOverflowKind ArithKind;
switch (builtinID) {
default:
llvm_unreachable("Unknown overflow builtin id.");
case Builtin::BI__builtin_uadd_overflow:
case Builtin::BI__builtin_uaddl_overflow:
case Builtin::BI__builtin_uaddll_overflow:
case Builtin::BI__builtin_sadd_overflow:
case Builtin::BI__builtin_saddl_overflow:
case Builtin::BI__builtin_saddll_overflow:
ArithKind = cir::BinOpOverflowKind::Add;
break;
case Builtin::BI__builtin_usub_overflow:
case Builtin::BI__builtin_usubl_overflow:
case Builtin::BI__builtin_usubll_overflow:
case Builtin::BI__builtin_ssub_overflow:
case Builtin::BI__builtin_ssubl_overflow:
case Builtin::BI__builtin_ssubll_overflow:
ArithKind = cir::BinOpOverflowKind::Sub;
break;
case Builtin::BI__builtin_umul_overflow:
case Builtin::BI__builtin_umull_overflow:
case Builtin::BI__builtin_umulll_overflow:
case Builtin::BI__builtin_smul_overflow:
case Builtin::BI__builtin_smull_overflow:
case Builtin::BI__builtin_smulll_overflow:
ArithKind = cir::BinOpOverflowKind::Mul;
break;
}

clang::QualType ResultQTy =
ResultArg->getType()->castAs<clang::PointerType>()->getPointeeType();
auto ResultCIRTy = mlir::cast<cir::IntType>(cgm.convertType(ResultQTy));

auto Loc = getLoc(e->getSourceRange());
cir::BinOpOverflowOp ArithOp =
cir::BinOpOverflowOp::create(builder, Loc, ResultCIRTy, ArithKind, X, Y);

bool isVolatile =
ResultArg->getType()->getPointeeType().isVolatileQualified();
builder.createStore(Loc, emitToMemory(ArithOp.getResult(), ResultQTy),
ResultPtr, isVolatile);

return RValue::get(ArithOp.getOverflow());
}
}

// If this is an alias for a lib function (e.g. __builtin_sin), emit
Expand Down
Loading