Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CIR][CIRGen][LLVMLowering] Add support for checked arithmetic builtins #560

Merged
merged 2 commits into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
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 CheckedBinOpResults {
mlir::Value result;
mlir::Value overflow;
};

CheckedBinOpResults createCheckedBinOp(mlir::Location loc,
bcardosolopes marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1079,6 +1079,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.
bcardosolopes marked this conversation as resolved.
Show resolved Hide resolved
}];

let arguments = (ins Arg<BinOpOverflowKind, "arithmetic kind">:$kind,
CIR_IntType:$lhs, CIR_IntType:$rhs);
bcardosolopes marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -89,6 +89,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 @@ -703,6 +749,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.createCheckedBinOp(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.
bcardosolopes marked this conversation as resolved.
Show resolved Hide resolved

// 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.createCheckedBinOp(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
Loading