Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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: 8 additions & 5 deletions clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,14 +300,17 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return cir::GlobalViewAttr::get(type, symbol, indices);
}

mlir::Value createGetGlobal(mlir::Location loc, cir::GlobalOp global) {
mlir::Value createGetGlobal(mlir::Location loc, cir::GlobalOp global,
bool threadLocal = false) {
assert(!cir::MissingFeatures::addressSpace());
return cir::GetGlobalOp::create(
*this, loc, getPointerTo(global.getSymType()), global.getSymName());
auto getGlobalOp = cir::GetGlobalOp::create(
*this, loc, getPointerTo(global.getSymType()), global.getSymNameAttr(),
threadLocal);
return getGlobalOp.getAddr();
}

mlir::Value createGetGlobal(cir::GlobalOp global) {
return createGetGlobal(global.getLoc(), global);
mlir::Value createGetGlobal(cir::GlobalOp global, bool threadLocal = false) {
return createGetGlobal(global.getLoc(), global, threadLocal);
}

/// Create a copy with inferred length.
Expand Down
93 changes: 92 additions & 1 deletion 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 `)` `:` type($lhs) `,`
`(` type($result) `,` 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 Expand Up @@ -1882,6 +1958,13 @@ def CIR_GlobalLinkageKind : CIR_I32EnumAttr<
// properties of a global variable will be added over time as more of ClangIR
// is upstreamed.

def CIR_TLSModel : CIR_I32EnumAttr<"TLS_Model", "TLS model", [
I32EnumAttrCase<"GeneralDynamic", 0, "tls_dyn">,
I32EnumAttrCase<"LocalDynamic", 1, "tls_local_dyn">,
I32EnumAttrCase<"InitialExec", 2, "tls_init_exec">,
I32EnumAttrCase<"LocalExec", 3, "tls_local_exec">
]>;

def CIR_GlobalOp : CIR_Op<"global", [
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
DeclareOpInterfaceMethods<CIRGlobalValueInterface>,
Expand Down Expand Up @@ -1910,6 +1993,7 @@ def CIR_GlobalOp : CIR_Op<"global", [
OptionalAttr<StrAttr>:$sym_visibility,
TypeAttr:$sym_type,
CIR_GlobalLinkageKind:$linkage,
OptionalAttr<CIR_TLSModel>:$tls_model,
OptionalAttr<AnyAttr>:$initial_value,
UnitAttr:$comdat,
UnitAttr:$constant,
Expand All @@ -1925,6 +2009,7 @@ def CIR_GlobalOp : CIR_Op<"global", [
(`constant` $constant^)?
$linkage
(`comdat` $comdat^)?
($tls_model^)?
(`dso_local` $dso_local^)?
$sym_name
custom<GlobalOpTypeAndInitialValue>($sym_type, $initial_value,
Expand Down Expand Up @@ -1988,16 +2073,22 @@ def CIR_GetGlobalOp : CIR_Op<"get_global", [
undefined. The resulting type must always be a `!cir.ptr<...>` type with the
same address space as the global variable.

Addresses of thread local globals can only be retrieved if this operation
is marked `thread_local`, which indicates the address isn't constant.

Example:
```mlir
%x = cir.get_global @gv : !cir.ptr<i32>
...
%y = cir.get_global thread_local @tls_gv : !cir.ptr<i32>
```
}];

let arguments = (ins FlatSymbolRefAttr:$name);
let arguments = (ins FlatSymbolRefAttr:$name, UnitAttr:$tls);
let results = (outs Res<CIR_PointerType, "", []>:$addr);

let assemblyFormat = [{
(`thread_local` $tls^)?
$name `:` qualified(type($addr)) attr-dict
}];
}
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());
cir::BinOpOverflowOp 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
3 changes: 2 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@ CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &d,
if (supportsCOMDAT() && gv.isWeakForLinker())
gv.setComdat(true);

assert(!cir::MissingFeatures::opGlobalThreadLocal());
if (d.getTLSKind())
llvm_unreachable("TLS mode is NYI");

setGVProperties(gv, &d);

Expand Down
7 changes: 4 additions & 3 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,6 @@ static LValue emitGlobalVarDeclLValue(CIRGenFunction &cgf, const Expr *e,
QualType t = e->getType();

// If it's thread_local, emit a call to its wrapper function instead.
assert(!cir::MissingFeatures::opGlobalThreadLocal());
if (vd->getTLSKind() == VarDecl::TLS_Dynamic)
cgf.cgm.errorNYI(e->getSourceRange(),
"emitGlobalVarDeclLValue: thread_local variable");
Expand Down Expand Up @@ -312,7 +311,8 @@ static LValue emitGlobalVarDeclLValue(CIRGenFunction &cgf, const Expr *e,
void CIRGenFunction::emitStoreOfScalar(mlir::Value value, Address addr,
bool isVolatile, QualType ty,
bool isInit, bool isNontemporal) {
assert(!cir::MissingFeatures::opLoadStoreThreadLocal());
// Traditional LLVM codegen handles thread local separately, CIR handles
// as part of getAddrOfGlobalVar (GetGlobalOp).

if (const auto *clangVecTy = ty->getAs<clang::VectorType>()) {
// Boolean vectors use `iN` as storage type.
Expand Down Expand Up @@ -556,7 +556,8 @@ void CIRGenFunction::emitStoreOfScalar(mlir::Value value, LValue lvalue,
mlir::Value CIRGenFunction::emitLoadOfScalar(Address addr, bool isVolatile,
QualType ty, SourceLocation loc,
LValueBaseInfo baseInfo) {
assert(!cir::MissingFeatures::opLoadStoreThreadLocal());
// Traditional LLVM codegen handles thread local separately, CIR handles
// as part of getAddrOfGlobalVar (GetGlobalOp).
mlir::Type eltTy = addr.getElementType();

if (const auto *clangVecTy = ty->getAs<clang::VectorType>()) {
Expand Down
Loading