Skip to content

Commit 7f45acf

Browse files
committed
[clang][Interp] Implement various overflow and carry builtins
Enough so we can enable SemaCXX/builtin-overflow.cpp.
1 parent fc38182 commit 7f45acf

File tree

4 files changed

+239
-3
lines changed

4 files changed

+239
-3
lines changed

clang/lib/AST/Interp/ByteCodeEmitter.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
using namespace clang;
2323
using namespace clang::interp;
2424

25+
static bool isUnevaluatedBuiltin(unsigned BuiltinID) {
26+
return BuiltinID == Builtin::BI__builtin_classify_type;
27+
}
28+
2529
Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
2630
bool IsLambdaStaticInvoker = false;
2731
if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl);
@@ -122,7 +126,7 @@ Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
122126
if (!Func) {
123127
bool IsUnevaluatedBuiltin = false;
124128
if (unsigned BI = FuncDecl->getBuiltinID())
125-
IsUnevaluatedBuiltin = Ctx.getASTContext().BuiltinInfo.isUnevaluated(BI);
129+
IsUnevaluatedBuiltin = isUnevaluatedBuiltin(BI);
126130

127131
Func =
128132
P.createFunction(FuncDecl, ParamOffset, std::move(ParamTypes),

clang/lib/AST/Interp/InterpBuiltin.cpp

Lines changed: 215 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ static APSInt peekToAPSInt(InterpStack &Stk, PrimType T, size_t Offset = 0) {
5555
APSInt R;
5656
INT_TYPE_SWITCH(T, {
5757
T Val = Stk.peek<T>(Offset);
58-
R = APSInt(
59-
APInt(Val.bitWidth(), static_cast<uint64_t>(Val), T::isSigned()));
58+
R = APSInt(APInt(Val.bitWidth(), static_cast<uint64_t>(Val), T::isSigned()),
59+
!T::isSigned());
6060
});
6161

6262
return R;
@@ -155,6 +155,11 @@ static void pushSizeT(InterpState &S, uint64_t Val) {
155155
}
156156
}
157157

158+
static void assignInteger(Pointer &Dest, PrimType ValueT, const APSInt &Value) {
159+
INT_TYPE_SWITCH_NO_BOOL(
160+
ValueT, { Dest.deref<T>() = T::from(static_cast<T>(Value)); });
161+
}
162+
158163
static bool retPrimValue(InterpState &S, CodePtr OpPC, APValue &Result,
159164
std::optional<PrimType> &T) {
160165
if (!T)
@@ -667,6 +672,175 @@ static bool interp__builtin_launder(InterpState &S, CodePtr OpPC,
667672
return true;
668673
}
669674

675+
// Two integral values followed by a pointer (lhs, rhs, resultOut)
676+
static bool interp__builtin_overflowop(InterpState &S, CodePtr OpPC,
677+
const InterpFrame *Frame,
678+
const Function *Func,
679+
const CallExpr *Call) {
680+
Pointer &ResultPtr = S.Stk.peek<Pointer>();
681+
if (ResultPtr.isDummy())
682+
return false;
683+
684+
unsigned BuiltinOp = Func->getBuiltinID();
685+
PrimType RHST = *S.getContext().classify(Call->getArg(1)->getType());
686+
PrimType LHST = *S.getContext().classify(Call->getArg(0)->getType());
687+
APSInt RHS = peekToAPSInt(S.Stk, RHST,
688+
align(primSize(PT_Ptr)) + align(primSize(RHST)));
689+
APSInt LHS = peekToAPSInt(S.Stk, LHST,
690+
align(primSize(PT_Ptr)) + align(primSize(RHST)) +
691+
align(primSize(LHST)));
692+
QualType ResultType = Call->getArg(2)->getType()->getPointeeType();
693+
PrimType ResultT = *S.getContext().classify(ResultType);
694+
bool Overflow;
695+
696+
APSInt Result;
697+
if (BuiltinOp == Builtin::BI__builtin_add_overflow ||
698+
BuiltinOp == Builtin::BI__builtin_sub_overflow ||
699+
BuiltinOp == Builtin::BI__builtin_mul_overflow) {
700+
bool IsSigned = LHS.isSigned() || RHS.isSigned() ||
701+
ResultType->isSignedIntegerOrEnumerationType();
702+
bool AllSigned = LHS.isSigned() && RHS.isSigned() &&
703+
ResultType->isSignedIntegerOrEnumerationType();
704+
uint64_t LHSSize = LHS.getBitWidth();
705+
uint64_t RHSSize = RHS.getBitWidth();
706+
uint64_t ResultSize = S.getCtx().getTypeSize(ResultType);
707+
uint64_t MaxBits = std::max(std::max(LHSSize, RHSSize), ResultSize);
708+
709+
// Add an additional bit if the signedness isn't uniformly agreed to. We
710+
// could do this ONLY if there is a signed and an unsigned that both have
711+
// MaxBits, but the code to check that is pretty nasty. The issue will be
712+
// caught in the shrink-to-result later anyway.
713+
if (IsSigned && !AllSigned)
714+
++MaxBits;
715+
716+
LHS = APSInt(LHS.extOrTrunc(MaxBits), !IsSigned);
717+
RHS = APSInt(RHS.extOrTrunc(MaxBits), !IsSigned);
718+
Result = APSInt(MaxBits, !IsSigned);
719+
}
720+
721+
// Find largest int.
722+
switch (BuiltinOp) {
723+
default:
724+
llvm_unreachable("Invalid value for BuiltinOp");
725+
case Builtin::BI__builtin_add_overflow:
726+
case Builtin::BI__builtin_sadd_overflow:
727+
case Builtin::BI__builtin_saddl_overflow:
728+
case Builtin::BI__builtin_saddll_overflow:
729+
case Builtin::BI__builtin_uadd_overflow:
730+
case Builtin::BI__builtin_uaddl_overflow:
731+
case Builtin::BI__builtin_uaddll_overflow:
732+
Result = LHS.isSigned() ? LHS.sadd_ov(RHS, Overflow)
733+
: LHS.uadd_ov(RHS, Overflow);
734+
break;
735+
case Builtin::BI__builtin_sub_overflow:
736+
case Builtin::BI__builtin_ssub_overflow:
737+
case Builtin::BI__builtin_ssubl_overflow:
738+
case Builtin::BI__builtin_ssubll_overflow:
739+
case Builtin::BI__builtin_usub_overflow:
740+
case Builtin::BI__builtin_usubl_overflow:
741+
case Builtin::BI__builtin_usubll_overflow:
742+
Result = LHS.isSigned() ? LHS.ssub_ov(RHS, Overflow)
743+
: LHS.usub_ov(RHS, Overflow);
744+
break;
745+
case Builtin::BI__builtin_mul_overflow:
746+
case Builtin::BI__builtin_smul_overflow:
747+
case Builtin::BI__builtin_smull_overflow:
748+
case Builtin::BI__builtin_smulll_overflow:
749+
case Builtin::BI__builtin_umul_overflow:
750+
case Builtin::BI__builtin_umull_overflow:
751+
case Builtin::BI__builtin_umulll_overflow:
752+
Result = LHS.isSigned() ? LHS.smul_ov(RHS, Overflow)
753+
: LHS.umul_ov(RHS, Overflow);
754+
break;
755+
}
756+
757+
// In the case where multiple sizes are allowed, truncate and see if
758+
// the values are the same.
759+
if (BuiltinOp == Builtin::BI__builtin_add_overflow ||
760+
BuiltinOp == Builtin::BI__builtin_sub_overflow ||
761+
BuiltinOp == Builtin::BI__builtin_mul_overflow) {
762+
// APSInt doesn't have a TruncOrSelf, so we use extOrTrunc instead,
763+
// since it will give us the behavior of a TruncOrSelf in the case where
764+
// its parameter <= its size. We previously set Result to be at least the
765+
// type-size of the result, so getTypeSize(ResultType) <= Resu
766+
APSInt Temp = Result.extOrTrunc(S.getCtx().getTypeSize(ResultType));
767+
Temp.setIsSigned(ResultType->isSignedIntegerOrEnumerationType());
768+
769+
if (!APSInt::isSameValue(Temp, Result))
770+
Overflow = true;
771+
Result = Temp;
772+
}
773+
774+
// Write Result to ResultPtr and put Overflow on the stacl.
775+
assignInteger(ResultPtr, ResultT, Result);
776+
ResultPtr.initialize();
777+
assert(Func->getDecl()->getReturnType()->isBooleanType());
778+
S.Stk.push<Boolean>(Overflow);
779+
return true;
780+
}
781+
782+
/// Three integral values followed by a pointer (lhs, rhs, carry, carryOut).
783+
static bool interp__builtin_carryop(InterpState &S, CodePtr OpPC,
784+
const InterpFrame *Frame,
785+
const Function *Func,
786+
const CallExpr *Call) {
787+
unsigned BuiltinOp = Func->getBuiltinID();
788+
PrimType LHST = *S.getContext().classify(Call->getArg(0)->getType());
789+
PrimType RHST = *S.getContext().classify(Call->getArg(1)->getType());
790+
PrimType CarryT = *S.getContext().classify(Call->getArg(2)->getType());
791+
APSInt RHS = peekToAPSInt(S.Stk, RHST,
792+
align(primSize(PT_Ptr)) + align(primSize(CarryT)) +
793+
align(primSize(RHST)));
794+
APSInt LHS =
795+
peekToAPSInt(S.Stk, LHST,
796+
align(primSize(PT_Ptr)) + align(primSize(RHST)) +
797+
align(primSize(CarryT)) + align(primSize(LHST)));
798+
APSInt CarryIn = peekToAPSInt(
799+
S.Stk, LHST, align(primSize(PT_Ptr)) + align(primSize(CarryT)));
800+
APSInt CarryOut;
801+
802+
APSInt Result;
803+
// Copy the number of bits and sign.
804+
Result = LHS;
805+
CarryOut = LHS;
806+
807+
bool FirstOverflowed = false;
808+
bool SecondOverflowed = false;
809+
switch (BuiltinOp) {
810+
default:
811+
llvm_unreachable("Invalid value for BuiltinOp");
812+
case Builtin::BI__builtin_addcb:
813+
case Builtin::BI__builtin_addcs:
814+
case Builtin::BI__builtin_addc:
815+
case Builtin::BI__builtin_addcl:
816+
case Builtin::BI__builtin_addcll:
817+
Result =
818+
LHS.uadd_ov(RHS, FirstOverflowed).uadd_ov(CarryIn, SecondOverflowed);
819+
break;
820+
case Builtin::BI__builtin_subcb:
821+
case Builtin::BI__builtin_subcs:
822+
case Builtin::BI__builtin_subc:
823+
case Builtin::BI__builtin_subcl:
824+
case Builtin::BI__builtin_subcll:
825+
Result =
826+
LHS.usub_ov(RHS, FirstOverflowed).usub_ov(CarryIn, SecondOverflowed);
827+
break;
828+
}
829+
// It is possible for both overflows to happen but CGBuiltin uses an OR so
830+
// this is consistent.
831+
CarryOut = (uint64_t)(FirstOverflowed | SecondOverflowed);
832+
833+
Pointer &CarryOutPtr = S.Stk.peek<Pointer>();
834+
QualType CarryOutType = Call->getArg(3)->getType()->getPointeeType();
835+
PrimType CarryOutT = *S.getContext().classify(CarryOutType);
836+
assignInteger(CarryOutPtr, CarryOutT, CarryOut);
837+
CarryOutPtr.initialize();
838+
839+
assert(Call->getType() == Call->getArg(0)->getType());
840+
pushAPSInt(S, Result);
841+
return true;
842+
}
843+
670844
bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
671845
const CallExpr *Call) {
672846
InterpFrame *Frame = S.Current;
@@ -901,6 +1075,45 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
9011075
return false;
9021076
break;
9031077

1078+
case Builtin::BI__builtin_add_overflow:
1079+
case Builtin::BI__builtin_sub_overflow:
1080+
case Builtin::BI__builtin_mul_overflow:
1081+
case Builtin::BI__builtin_sadd_overflow:
1082+
case Builtin::BI__builtin_uadd_overflow:
1083+
case Builtin::BI__builtin_uaddl_overflow:
1084+
case Builtin::BI__builtin_uaddll_overflow:
1085+
case Builtin::BI__builtin_usub_overflow:
1086+
case Builtin::BI__builtin_usubl_overflow:
1087+
case Builtin::BI__builtin_usubll_overflow:
1088+
case Builtin::BI__builtin_umul_overflow:
1089+
case Builtin::BI__builtin_umull_overflow:
1090+
case Builtin::BI__builtin_umulll_overflow:
1091+
case Builtin::BI__builtin_saddl_overflow:
1092+
case Builtin::BI__builtin_saddll_overflow:
1093+
case Builtin::BI__builtin_ssub_overflow:
1094+
case Builtin::BI__builtin_ssubl_overflow:
1095+
case Builtin::BI__builtin_ssubll_overflow:
1096+
case Builtin::BI__builtin_smul_overflow:
1097+
case Builtin::BI__builtin_smull_overflow:
1098+
case Builtin::BI__builtin_smulll_overflow:
1099+
if (!interp__builtin_overflowop(S, OpPC, Frame, F, Call))
1100+
return false;
1101+
break;
1102+
1103+
case Builtin::BI__builtin_addcb:
1104+
case Builtin::BI__builtin_addcs:
1105+
case Builtin::BI__builtin_addc:
1106+
case Builtin::BI__builtin_addcl:
1107+
case Builtin::BI__builtin_addcll:
1108+
case Builtin::BI__builtin_subcb:
1109+
case Builtin::BI__builtin_subcs:
1110+
case Builtin::BI__builtin_subc:
1111+
case Builtin::BI__builtin_subcl:
1112+
case Builtin::BI__builtin_subcll:
1113+
if (!interp__builtin_carryop(S, OpPC, Frame, F, Call))
1114+
return false;
1115+
break;
1116+
9041117
default:
9051118
return false;
9061119
}

clang/lib/AST/Interp/PrimType.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,24 @@ static inline bool aligned(const void *P) {
149149
} \
150150
} while (0)
151151

152+
#define INT_TYPE_SWITCH_NO_BOOL(Expr, B) \
153+
do { \
154+
switch (Expr) { \
155+
TYPE_SWITCH_CASE(PT_Sint8, B) \
156+
TYPE_SWITCH_CASE(PT_Uint8, B) \
157+
TYPE_SWITCH_CASE(PT_Sint16, B) \
158+
TYPE_SWITCH_CASE(PT_Uint16, B) \
159+
TYPE_SWITCH_CASE(PT_Sint32, B) \
160+
TYPE_SWITCH_CASE(PT_Uint32, B) \
161+
TYPE_SWITCH_CASE(PT_Sint64, B) \
162+
TYPE_SWITCH_CASE(PT_Uint64, B) \
163+
TYPE_SWITCH_CASE(PT_IntAP, B) \
164+
TYPE_SWITCH_CASE(PT_IntAPS, B) \
165+
default: \
166+
llvm_unreachable("Not an integer value"); \
167+
} \
168+
} while (0)
169+
152170
#define COMPOSITE_TYPE_SWITCH(Expr, B, D) \
153171
do { \
154172
switch (Expr) { \

clang/test/SemaCXX/builtins-overflow.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify %s
2+
// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify %s -fexperimental-new-constant-interpreter
23
// expected-no-diagnostics
34

45
#include <limits.h>

0 commit comments

Comments
 (0)