Skip to content

Commit

Permalink
[Sema][CodeGen] Support __builtin_<op>_overflow with __intcap
Browse files Browse the repository at this point in the history
Morello LLVM has downstream support for this, but it's both incomplete
(see https://git.morello-project.org/morello/llvm-project/-/issues/80)
and incorrect with regards to provenance (in that it takes a naive
type-based approach rather than considering the cheri_no_provenance
attribute, meaning it differs from the binary operators in provenance
semantics). This is a from-scratch implementation that aims to not have
the same shortcomings.
  • Loading branch information
jrtc27 committed Jul 31, 2024
1 parent 0621ed3 commit 89bb014
Show file tree
Hide file tree
Showing 4 changed files with 5,733 additions and 51 deletions.
217 changes: 166 additions & 51 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,28 +688,26 @@ static Value *emitRangedBuiltin(CodeGenFunction &CGF,
}

namespace {
struct WidthAndSignedness {
unsigned Width;
struct RangeAndSignedness {
unsigned Range;
bool Signed;
};
}

static WidthAndSignedness
getIntegerWidthAndSignedness(const clang::ASTContext &context,
static RangeAndSignedness
getIntegerRangeAndSignedness(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;
unsigned Range = context.getIntRange(Type);
bool Signed = Type->isSignedIntegerType();
return {Width, Signed};
return {Range, 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) {
static struct RangeAndSignedness
EncompassingIntegerType(ArrayRef<struct RangeAndSignedness> Types) {
assert(Types.size() > 0 && "Empty list of types.");

// If any of the given types is signed, we must return a signed type.
Expand All @@ -722,15 +720,15 @@ EncompassingIntegerType(ArrayRef<struct WidthAndSignedness> Types) {
// 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;
unsigned Range = 0;
for (const auto &Type : Types) {
unsigned MinWidth = Type.Width + (Signed && !Type.Signed);
if (Width < MinWidth) {
Width = MinWidth;
unsigned MinRange = Type.Range + (Signed && !Type.Signed);
if (Range < MinRange) {
Range = MinRange;
}
}

return {Width, Signed};
return {Range, Signed};
}

Value *CodeGenFunction::EmitVAStartEnd(Value *ArgValue, bool IsStart) {
Expand Down Expand Up @@ -1914,38 +1912,71 @@ RValue CodeGenFunction::emitBuiltinOSLogFormat(const CallExpr &E) {
}

static bool isSpecialUnsignedMultiplySignedResult(
unsigned BuiltinID, WidthAndSignedness Op1Info, WidthAndSignedness Op2Info,
WidthAndSignedness ResultInfo) {
unsigned BuiltinID, RangeAndSignedness Op1Info, RangeAndSignedness Op2Info,
RangeAndSignedness ResultInfo) {
return BuiltinID == Builtin::BI__builtin_mul_overflow &&
Op1Info.Width == Op2Info.Width && Op2Info.Width == ResultInfo.Width &&
Op1Info.Range == Op2Info.Range && Op2Info.Range == ResultInfo.Range &&
!Op1Info.Signed && !Op2Info.Signed && ResultInfo.Signed;
}

static RValue EmitCheckedUnsignedMultiplySignedResult(
CodeGenFunction &CGF, const clang::Expr *Op1, WidthAndSignedness Op1Info,
const clang::Expr *Op2, WidthAndSignedness Op2Info,
CodeGenFunction &CGF, const clang::Expr *Op1, RangeAndSignedness Op1Info,
const clang::Expr *Op2, RangeAndSignedness Op2Info,
const clang::Expr *ResultArg, QualType ResultQTy,
WidthAndSignedness ResultInfo) {
RangeAndSignedness ResultInfo, SourceLocation Loc) {
assert(isSpecialUnsignedMultiplySignedResult(
Builtin::BI__builtin_mul_overflow, Op1Info, Op2Info, ResultInfo) &&
"Cannot specialize this multiply");

clang::QualType Op1QTy = Op1->getType();
clang::QualType Op2QTy = Op2->getType();
bool Op1IsCap = Op1QTy->isCHERICapabilityType(CGF.getContext());
bool Op2IsCap = Op2QTy->isCHERICapabilityType(CGF.getContext());
bool ResultIsCap = ResultQTy->isCHERICapabilityType(CGF.getContext());

llvm::Value *V1 = CGF.EmitScalarExpr(Op1);
llvm::Value *V2 = CGF.EmitScalarExpr(Op2);

llvm::Value *ProvenanceCap;
if (ResultIsCap && (Op1IsCap || Op2IsCap)) {
bool Op1NoProvenance =
!Op1IsCap || Op1QTy->hasAttr(attr::CHERINoProvenance);
bool Op2NoProvenance =
!Op2IsCap || Op2QTy->hasAttr(attr::CHERINoProvenance);
if (Op1NoProvenance && Op2NoProvenance)
ProvenanceCap = nullptr;
else if (Op1NoProvenance)
ProvenanceCap = V2;
else
ProvenanceCap = V1;
} else
ProvenanceCap = nullptr;

if (Op1IsCap)
V1 = CGF.getCapabilityIntegerValue(V1);

if (Op2IsCap)
V2 = CGF.getCapabilityIntegerValue(V2);

llvm::Value *HasOverflow;
llvm::Value *Result = EmitOverflowIntrinsic(
CGF, llvm::Intrinsic::umul_with_overflow, V1, V2, HasOverflow);

// The intrinsic call will detect overflow when the value is > UINT_MAX,
// however, since the original builtin had a signed result, we need to report
// an overflow when the result is greater than INT_MAX.
auto IntMax = llvm::APInt::getSignedMaxValue(ResultInfo.Width);
auto IntMax = llvm::APInt::getSignedMaxValue(ResultInfo.Range);
llvm::Value *IntMaxValue = llvm::ConstantInt::get(Result->getType(), IntMax);

llvm::Value *IntMaxOverflow = CGF.Builder.CreateICmpUGT(Result, IntMaxValue);
HasOverflow = CGF.Builder.CreateOr(HasOverflow, IntMaxOverflow);

if (ResultIsCap)
Result = ProvenanceCap
? CGF.setCapabilityIntegerValue(ProvenanceCap, Result, Loc)
: CGF.getNullDerivedCapability(
CGF.CGM.getTypes().ConvertType(ResultQTy), Result);

bool isVolatile =
ResultArg->getType()->getPointeeType().isVolatileQualified();
Address ResultPtr = CGF.EmitPointerWithAlignment(ResultArg);
Expand All @@ -1956,45 +1987,77 @@ static RValue EmitCheckedUnsignedMultiplySignedResult(

/// Determine if a binop is a checked mixed-sign multiply we can specialize.
static bool isSpecialMixedSignMultiply(unsigned BuiltinID,
WidthAndSignedness Op1Info,
WidthAndSignedness Op2Info,
WidthAndSignedness ResultInfo) {
RangeAndSignedness Op1Info,
RangeAndSignedness Op2Info,
RangeAndSignedness ResultInfo) {
return BuiltinID == Builtin::BI__builtin_mul_overflow &&
std::max(Op1Info.Width, Op2Info.Width) >= ResultInfo.Width &&
std::max(Op1Info.Range, Op2Info.Range) >= ResultInfo.Range &&
Op1Info.Signed != Op2Info.Signed;
}

/// Emit a checked mixed-sign multiply. This is a cheaper specialization of
/// the generic checked-binop irgen.
static RValue
EmitCheckedMixedSignMultiply(CodeGenFunction &CGF, const clang::Expr *Op1,
WidthAndSignedness Op1Info, const clang::Expr *Op2,
WidthAndSignedness Op2Info,
RangeAndSignedness Op1Info, const clang::Expr *Op2,
RangeAndSignedness Op2Info,
const clang::Expr *ResultArg, QualType ResultQTy,
WidthAndSignedness ResultInfo) {
RangeAndSignedness ResultInfo,
SourceLocation Loc) {
assert(isSpecialMixedSignMultiply(Builtin::BI__builtin_mul_overflow, Op1Info,
Op2Info, ResultInfo) &&
"Not a mixed-sign multipliction we can specialize");

QualType Op1QTy = Op1->getType();
QualType Op2QTy = Op2->getType();
bool Op1IsCap = Op1QTy->isCHERICapabilityType(CGF.getContext());
bool Op2IsCap = Op2QTy->isCHERICapabilityType(CGF.getContext());
bool ResultIsCap = ResultQTy->isCHERICapabilityType(CGF.getContext());

// Emit the signed and unsigned operands.
const clang::Expr *SignedOp = Op1Info.Signed ? Op1 : Op2;
const clang::Expr *UnsignedOp = Op1Info.Signed ? Op2 : Op1;
llvm::Value *Signed = CGF.EmitScalarExpr(SignedOp);
llvm::Value *Unsigned = CGF.EmitScalarExpr(UnsignedOp);
unsigned SignedOpWidth = Op1Info.Signed ? Op1Info.Width : Op2Info.Width;
unsigned UnsignedOpWidth = Op1Info.Signed ? Op2Info.Width : Op1Info.Width;
unsigned SignedOpRange = Op1Info.Signed ? Op1Info.Range : Op2Info.Range;
unsigned UnsignedOpRange = Op1Info.Signed ? Op2Info.Range : Op1Info.Range;
bool SignedIsCap = Op1Info.Signed ? Op1IsCap : Op2IsCap;
bool UnsignedIsCap = Op1Info.Signed ? Op2IsCap : Op1IsCap;

llvm::Value *ProvenanceCap;
if (ResultIsCap && (Op1IsCap || Op2IsCap)) {
bool Op1NoProvenance =
!Op1IsCap || Op1QTy->hasAttr(attr::CHERINoProvenance);
bool Op2NoProvenance =
!Op2IsCap || Op2QTy->hasAttr(attr::CHERINoProvenance);
if (Op1NoProvenance && Op2NoProvenance)
ProvenanceCap = nullptr;
else if (Op1NoProvenance)
ProvenanceCap = Op1Info.Signed ? Unsigned : Signed;
else
ProvenanceCap = Op1Info.Signed ? Signed : Unsigned;
} else
ProvenanceCap = nullptr;

if (SignedIsCap)
Signed = CGF.getCapabilityIntegerValue(Signed);

if (UnsignedIsCap)
Unsigned = CGF.getCapabilityIntegerValue(Unsigned);

// One of the operands may be smaller than the other. If so, [s|z]ext it.
if (SignedOpWidth < UnsignedOpWidth)
if (SignedOpRange < UnsignedOpRange)
Signed = CGF.Builder.CreateSExt(Signed, Unsigned->getType(), "op.sext");
if (UnsignedOpWidth < SignedOpWidth)
if (UnsignedOpRange < SignedOpRange)
Unsigned = CGF.Builder.CreateZExt(Unsigned, Signed->getType(), "op.zext");

llvm::Type *OpTy = Signed->getType();
llvm::Value *Zero = llvm::Constant::getNullValue(OpTy);
Address ResultPtr = CGF.EmitPointerWithAlignment(ResultArg);
llvm::Type *ResTy = ResultPtr.getElementType();
unsigned OpWidth = std::max(Op1Info.Width, Op2Info.Width);
llvm::Type *ResTy = ResultIsCap ? llvm::IntegerType::get(CGF.getLLVMContext(),
ResultInfo.Range)
: ResultPtr.getElementType();
unsigned OpRange = std::max(Op1Info.Range, Op2Info.Range);

// Take the absolute value of the signed operand.
llvm::Value *IsNegative = CGF.Builder.CreateICmpSLT(Signed, Zero);
Expand All @@ -2013,7 +2076,7 @@ EmitCheckedMixedSignMultiply(CodeGenFunction &CGF, const clang::Expr *Op1,
// Signed overflow occurs if the result is greater than INT_MAX or lesser
// than INT_MIN, i.e when |Result| > (INT_MAX + IsNegative).
auto IntMax =
llvm::APInt::getSignedMaxValue(ResultInfo.Width).zext(OpWidth);
llvm::APInt::getSignedMaxValue(ResultInfo.Range).zext(OpRange);
llvm::Value *MaxResult =
CGF.Builder.CreateAdd(llvm::ConstantInt::get(OpTy, IntMax),
CGF.Builder.CreateZExt(IsNegative, OpTy));
Expand All @@ -2031,9 +2094,8 @@ EmitCheckedMixedSignMultiply(CodeGenFunction &CGF, const clang::Expr *Op1,
llvm::Value *Underflow = CGF.Builder.CreateAnd(
IsNegative, CGF.Builder.CreateIsNotNull(UnsignedResult));
Overflow = CGF.Builder.CreateOr(UnsignedOverflow, Underflow);
if (ResultInfo.Width < OpWidth) {
auto IntMax =
llvm::APInt::getMaxValue(ResultInfo.Width).zext(OpWidth);
if (ResultInfo.Range < OpRange) {
auto IntMax = llvm::APInt::getMaxValue(ResultInfo.Range).zext(OpRange);
llvm::Value *TruncOverflow = CGF.Builder.CreateICmpUGT(
UnsignedResult, llvm::ConstantInt::get(OpTy, IntMax));
Overflow = CGF.Builder.CreateOr(Overflow, TruncOverflow);
Expand All @@ -2047,6 +2109,12 @@ EmitCheckedMixedSignMultiply(CodeGenFunction &CGF, const clang::Expr *Op1,
}
assert(Overflow && Result && "Missing overflow or result");

if (ResultIsCap)
Result = ProvenanceCap
? CGF.setCapabilityIntegerValue(ProvenanceCap, Result, Loc)
: CGF.getNullDerivedCapability(
CGF.CGM.getTypes().ConvertType(ResultQTy), Result);

bool isVolatile =
ResultArg->getType()->getPointeeType().isVolatileQualified();
CGF.Builder.CreateStore(CGF.EmitToMemory(Result, ResultQTy), ResultPtr,
Expand Down Expand Up @@ -4493,52 +4561,64 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
const clang::Expr *RightArg = E->getArg(1);
const clang::Expr *ResultArg = E->getArg(2);

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

WidthAndSignedness LeftInfo =
getIntegerWidthAndSignedness(CGM.getContext(), LeftArg->getType());
WidthAndSignedness RightInfo =
getIntegerWidthAndSignedness(CGM.getContext(), RightArg->getType());
WidthAndSignedness ResultInfo =
getIntegerWidthAndSignedness(CGM.getContext(), ResultQTy);
bool LeftIsCap = LeftQTy->isCHERICapabilityType(CGM.getContext());
bool RightIsCap = RightQTy->isCHERICapabilityType(CGM.getContext());
bool ResultIsCap = ResultQTy->isCHERICapabilityType(CGM.getContext());
RangeAndSignedness LeftInfo =
getIntegerRangeAndSignedness(CGM.getContext(), LeftQTy);
RangeAndSignedness RightInfo =
getIntegerRangeAndSignedness(CGM.getContext(), RightQTy);
RangeAndSignedness ResultInfo =
getIntegerRangeAndSignedness(CGM.getContext(), ResultQTy);

// Handle mixed-sign multiplication as a special case, because adding
// runtime or backend support for our generic irgen would be too expensive.
if (isSpecialMixedSignMultiply(BuiltinID, LeftInfo, RightInfo, ResultInfo))
return EmitCheckedMixedSignMultiply(*this, LeftArg, LeftInfo, RightArg,
RightInfo, ResultArg, ResultQTy,
ResultInfo);
ResultInfo, E->getExprLoc());

if (isSpecialUnsignedMultiplySignedResult(BuiltinID, LeftInfo, RightInfo,
ResultInfo))
return EmitCheckedUnsignedMultiplySignedResult(
*this, LeftArg, LeftInfo, RightArg, RightInfo, ResultArg, ResultQTy,
ResultInfo);
ResultInfo, E->getExprLoc());

WidthAndSignedness EncompassingInfo =
RangeAndSignedness EncompassingInfo =
EncompassingIntegerType({LeftInfo, RightInfo, ResultInfo});

llvm::Type *EncompassingLLVMTy =
llvm::IntegerType::get(CGM.getLLVMContext(), EncompassingInfo.Width);
llvm::IntegerType::get(CGM.getLLVMContext(), EncompassingInfo.Range);

llvm::Type *ResultLLVMTy = CGM.getTypes().ConvertType(ResultQTy);
llvm::Type *ResultLLVMTy =
ResultIsCap
? llvm::IntegerType::get(CGM.getLLVMContext(), ResultInfo.Range)
: CGM.getTypes().ConvertType(ResultQTy);

llvm::Intrinsic::ID IntrinsicId;
bool Commutative;
switch (BuiltinID) {
default:
llvm_unreachable("Unknown overflow builtin id.");
case Builtin::BI__builtin_add_overflow:
Commutative = true;
IntrinsicId = EncompassingInfo.Signed
? llvm::Intrinsic::sadd_with_overflow
: llvm::Intrinsic::uadd_with_overflow;
break;
case Builtin::BI__builtin_sub_overflow:
Commutative = false;
IntrinsicId = EncompassingInfo.Signed
? llvm::Intrinsic::ssub_with_overflow
: llvm::Intrinsic::usub_with_overflow;
break;
case Builtin::BI__builtin_mul_overflow:
Commutative = true;
IntrinsicId = EncompassingInfo.Signed
? llvm::Intrinsic::smul_with_overflow
: llvm::Intrinsic::umul_with_overflow;
Expand All @@ -4549,6 +4629,34 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
llvm::Value *Right = EmitScalarExpr(RightArg);
Address ResultPtr = EmitPointerWithAlignment(ResultArg);

llvm::Value *ProvenanceCap;
if (ResultIsCap && (LeftIsCap || RightIsCap)) {
if (!Commutative) {
if (LeftIsCap)
ProvenanceCap = Left;
else
ProvenanceCap = nullptr;
} else {
bool LeftNoProvenance =
!LeftIsCap || LeftQTy->hasAttr(attr::CHERINoProvenance);
bool RightNoProvenance =
!RightIsCap || RightQTy->hasAttr(attr::CHERINoProvenance);
if (LeftNoProvenance && RightNoProvenance)
ProvenanceCap = nullptr;
else if (LeftNoProvenance)
ProvenanceCap = Right;
else
ProvenanceCap = Left;
}
} else
ProvenanceCap = nullptr;

if (LeftIsCap)
Left = getCapabilityIntegerValue(Left);

if (RightIsCap)
Right = getCapabilityIntegerValue(Right);

// Extend each operand to the encompassing type.
Left = Builder.CreateIntCast(Left, EncompassingLLVMTy, LeftInfo.Signed);
Right = Builder.CreateIntCast(Right, EncompassingLLVMTy, RightInfo.Signed);
Expand All @@ -4557,7 +4665,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
llvm::Value *Overflow, *Result;
Result = EmitOverflowIntrinsic(*this, IntrinsicId, Left, Right, Overflow);

if (EncompassingInfo.Width > ResultInfo.Width) {
if (EncompassingInfo.Range > ResultInfo.Range) {
// The encompassing type is wider than the result type, so we need to
// truncate it.
llvm::Value *ResultTrunc = Builder.CreateTrunc(Result, ResultLLVMTy);
Expand All @@ -4573,6 +4681,13 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
Result = ResultTrunc;
}

if (ResultIsCap)
Result = ProvenanceCap
? setCapabilityIntegerValue(ProvenanceCap, Result,
E->getExprLoc())
: getNullDerivedCapability(
CGM.getTypes().ConvertType(ResultQTy), Result);

// Finally, store the result using the pointer.
bool isVolatile =
ResultArg->getType()->getPointeeType().isVolatileQualified();
Expand Down
Loading

0 comments on commit 89bb014

Please sign in to comment.