Skip to content

Commit

Permalink
[CIR][CIRGen][LLVMLowering] Add support for checked arithmetic builti…
Browse files Browse the repository at this point in the history
…ns (#560)

This patch adds support for checked arithmetic builtins, including:

  - `__builtin_add_overflow` and `__builtin_{s|u}add{|l|ll}_overflow`;
  - `__builtin_sub_overflow` and `__builtin_{s|u}sub{|l|ll}_overflow`;
  - `__builtin_mul_overflow` and `__builtin_{s|u}mul{|l|ll}_overflow`.

This patch adds a new operation `cir.checked_arith` to represent these
builtins. Unlike other CIR operations, this new operation has two result
values. One for the possibly truncated result, and the other for a
boolean flag that indicates whether the operation has overflowed.

CIRGen and LLVMIR lowering support for the new operation is both
included in this PR.
  • Loading branch information
Lancern authored May 5, 2024
1 parent 981c976 commit f9b1067
Show file tree
Hide file tree
Showing 6 changed files with 836 additions and 7 deletions.
13 changes: 13 additions & 0 deletions clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,19 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return createSub(lhs, rhs, false, true);
}

struct BinOpOverflowResults {
mlir::Value result;
mlir::Value overflow;
};

BinOpOverflowResults createBinOpOverflowOp(mlir::Location loc,
mlir::cir::IntType resultTy,
mlir::cir::BinOpOverflowKind kind,
mlir::Value lhs, mlir::Value rhs) {
auto op = create<mlir::cir::BinOpOverflowOp>(loc, resultTy, kind, lhs, rhs);
return {op.getResult(), op.getOverflow()};
}

//===--------------------------------------------------------------------===//
// Cast/Conversion Operators
//===--------------------------------------------------------------------===//
Expand Down
58 changes: 58 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,64 @@ def CmpOp : CIR_Op<"cmp", [Pure, SameTypeOperands]> {
let hasVerifier = 0;
}

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

def BinOpOverflowKind : I32EnumAttr<
"BinOpOverflowKind",
"checked binary arithmetic operation kind",
[BinOpKind_Add, BinOpKind_Sub, BinOpKind_Mul]> {
let cppNamespace = "::mlir::cir";
}

def 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 Arg<BinOpOverflowKind, "arithmetic kind">:$kind,
CIR_IntType:$lhs, CIR_IntType:$rhs);
let results = (outs CIR_IntType:$result, CIR_BoolType:$overflow);

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

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

//===----------------------------------------------------------------------===//
// BitsOp
//===----------------------------------------------------------------------===//
Expand Down
197 changes: 197 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,52 @@ static void initializeAlloca(CIRGenFunction &CGF,
}
}

namespace {
struct WidthAndSignedness {
unsigned Width;
bool Signed;
};
} // namespace

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

// 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 Signed = false;
for (const auto &Type : Types) {
Signed |= Type.Signed;
}

// 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) {
unsigned MinWidth = Type.Width + (Signed && !Type.Signed);
if (Width < MinWidth) {
Width = MinWidth;
}
}

return {Width, Signed};
}

RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
const CallExpr *E,
ReturnValueSlot ReturnValue) {
Expand Down Expand Up @@ -705,6 +751,157 @@ RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
return RValue::get(
builder.createBitcast(AllocaAddr, builder.getVoidPtrTy()));
}

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 = mlir::cir::IntType::get(
builder.getContext(), EncompassingInfo.Width, EncompassingInfo.Signed);
auto ResultCIRTy =
CGM.getTypes().ConvertType(ResultQTy).cast<mlir::cir::IntType>();

mlir::Value Left = buildScalarExpr(LeftArg);
mlir::Value Right = buildScalarExpr(RightArg);
Address ResultPtr = buildPointerWithAlignment(ResultArg);

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

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

auto Loc = getLoc(E->getSourceRange());
auto ArithResult =
builder.createBinOpOverflowOp(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, buildToMemory(ArithResult.result, ResultQTy),
ResultPtr, isVolatile);

return RValue::get(ArithResult.overflow);
}

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 = buildScalarExpr(E->getArg(0));
mlir::Value Y = buildScalarExpr(E->getArg(1));

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

// Decide which of the arithmetic operation we are lowering to:
mlir::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 = mlir::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 = mlir::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 = mlir::cir::BinOpOverflowKind::Mul;
break;
}

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

auto Loc = getLoc(E->getSourceRange());
auto ArithResult =
builder.createBinOpOverflowOp(Loc, ResultCIRTy, ArithKind, X, Y);

bool isVolatile =
ResultArg->getType()->getPointeeType().isVolatileQualified();
builder.createStore(Loc, buildToMemory(ArithResult.result, ResultQTy),
ResultPtr, isVolatile);

return RValue::get(ArithResult.overflow);
}
}

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

0 comments on commit f9b1067

Please sign in to comment.