Skip to content

Commit

Permalink
[clang][Interp] Implement left and right shifts
Browse files Browse the repository at this point in the history
Differential Revision: https://reviews.llvm.org/D136532
  • Loading branch information
tbaederr committed Oct 30, 2022
1 parent 62efe45 commit 6d965c9
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 61 deletions.
10 changes: 10 additions & 0 deletions clang/lib/AST/Interp/ByteCodeExprGen.cpp
Expand Up @@ -237,6 +237,10 @@ bool ByteCodeExprGen<Emitter>::VisitBinaryOperator(const BinaryOperator *BO) {
return Discard(this->emitBitAnd(*T, BO));
case BO_Or:
return Discard(this->emitBitOr(*T, BO));
case BO_Shl:
return Discard(this->emitShl(*LT, *RT, BO));
case BO_Shr:
return Discard(this->emitShr(*LT, *RT, BO));
case BO_LAnd:
case BO_LOr:
default:
Expand Down Expand Up @@ -451,7 +455,13 @@ bool ByteCodeExprGen<Emitter>::VisitCompoundAssignOperator(
case BO_DivAssign:
case BO_RemAssign:
case BO_ShlAssign:
if (!this->emitShl(*LT, *RT, E))
return false;
break;
case BO_ShrAssign:
if (!this->emitShr(*LT, *RT, E))
return false;
break;
case BO_AndAssign:
case BO_XorAssign:
case BO_OrAssign:
Expand Down
101 changes: 40 additions & 61 deletions clang/lib/AST/Interp/Interp.h
Expand Up @@ -1103,84 +1103,63 @@ inline bool This(InterpState &S, CodePtr OpPC) {
// Shr, Shl
//===----------------------------------------------------------------------===//

template <PrimType TR, PrimType TL, class T = typename PrimConv<TR>::T>
unsigned Trunc(InterpState &S, CodePtr OpPC, unsigned Bits, const T &V) {
template <PrimType NameL, PrimType NameR>
inline bool Shr(InterpState &S, CodePtr OpPC) {
using LT = typename PrimConv<NameL>::T;
using RT = typename PrimConv<NameR>::T;
const auto &RHS = S.Stk.pop<RT>();
const auto &LHS = S.Stk.pop<LT>();
const unsigned Bits = LHS.bitWidth();

if (RHS.isNegative()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt();
return false;
}

// C++11 [expr.shift]p1: Shift width must be less than the bit width of
// the shifted type.
if (Bits > 1 && V >= T::from(Bits, V.bitWidth())) {
if (Bits > 1 && RHS >= RT::from(Bits, RHS.bitWidth())) {
const Expr *E = S.Current->getExpr(OpPC);
const APSInt Val = V.toAPSInt();
const APSInt Val = RHS.toAPSInt();
QualType Ty = E->getType();
S.CCEDiag(E, diag::note_constexpr_large_shift) << Val << Ty << Bits;
return Bits;
} else {
return static_cast<unsigned>(V);
}
}

template <PrimType TL, PrimType TR, typename T = typename PrimConv<TL>::T>
inline bool ShiftRight(InterpState &S, CodePtr OpPC, const T &V, unsigned RHS) {
if (RHS >= V.bitWidth()) {
S.Stk.push<T>(T::from(0, V.bitWidth()));
} else {
S.Stk.push<T>(T::from(V >> RHS, V.bitWidth()));
}
return true;
}

template <PrimType TL, PrimType TR, typename T = typename PrimConv<TL>::T>
inline bool ShiftLeft(InterpState &S, CodePtr OpPC, const T &V, unsigned RHS) {
if (V.isSigned() && !S.getLangOpts().CPlusPlus20) {
// C++11 [expr.shift]p2: A signed left shift must have a non-negative
// operand, and must not overflow the corresponding unsigned type.
// C++2a [expr.shift]p2: E1 << E2 is the unique value congruent to
// E1 x 2^E2 module 2^N.
if (V.isNegative()) {
const Expr *E = S.Current->getExpr(OpPC);
S.CCEDiag(E, diag::note_constexpr_lshift_of_negative) << V.toAPSInt();
} else if (V.countLeadingZeros() < RHS) {
S.CCEDiag(S.Current->getExpr(OpPC), diag::note_constexpr_lshift_discards);
}
return false;
}

if (V.bitWidth() == 1) {
S.Stk.push<T>(V);
} else if (RHS >= V.bitWidth()) {
S.Stk.push<T>(T::from(0, V.bitWidth()));
} else {
S.Stk.push<T>(T::from(V.toUnsigned() << RHS, V.bitWidth()));
}
unsigned URHS = static_cast<unsigned>(RHS);
S.Stk.push<LT>(LT::from(static_cast<unsigned>(LHS) >> URHS, LHS.bitWidth()));
return true;
}

template <PrimType TL, PrimType TR>
inline bool Shr(InterpState &S, CodePtr OpPC) {
const auto &RHS = S.Stk.pop<typename PrimConv<TR>::T>();
const auto &LHS = S.Stk.pop<typename PrimConv<TL>::T>();
template <PrimType NameL, PrimType NameR>
inline bool Shl(InterpState &S, CodePtr OpPC) {
using LT = typename PrimConv<NameL>::T;
using RT = typename PrimConv<NameR>::T;
const auto &RHS = S.Stk.pop<RT>();
const auto &LHS = S.Stk.pop<LT>();
const unsigned Bits = LHS.bitWidth();

if (RHS.isSigned() && RHS.isNegative()) {
if (RHS.isNegative()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt();
return ShiftLeft<TL, TR>(S, OpPC, LHS, Trunc<TR, TL>(S, OpPC, Bits, -RHS));
} else {
return ShiftRight<TL, TR>(S, OpPC, LHS, Trunc<TR, TL>(S, OpPC, Bits, RHS));
return false;
}
}

template <PrimType TL, PrimType TR>
inline bool Shl(InterpState &S, CodePtr OpPC) {
const auto &RHS = S.Stk.pop<typename PrimConv<TR>::T>();
const auto &LHS = S.Stk.pop<typename PrimConv<TL>::T>();
const unsigned Bits = LHS.bitWidth();

if (RHS.isSigned() && RHS.isNegative()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt();
return ShiftRight<TL, TR>(S, OpPC, LHS, Trunc<TR, TL>(S, OpPC, Bits, -RHS));
} else {
return ShiftLeft<TL, TR>(S, OpPC, LHS, Trunc<TR, TL>(S, OpPC, Bits, RHS));
// C++11 [expr.shift]p1: Shift width must be less than the bit width of
// the shifted type.
if (Bits > 1 && RHS >= RT::from(Bits, RHS.bitWidth())) {
const Expr *E = S.Current->getExpr(OpPC);
const APSInt Val = RHS.toAPSInt();
QualType Ty = E->getType();
S.CCEDiag(E, diag::note_constexpr_large_shift) << Val << Ty << Bits;
return false;
}

unsigned URHS = static_cast<unsigned>(RHS);
S.Stk.push<LT>(LT::from(static_cast<unsigned>(LHS) << URHS, LHS.bitWidth()));

return true;
}

//===----------------------------------------------------------------------===//
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/AST/Interp/Opcodes.td
Expand Up @@ -402,6 +402,17 @@ def Rem : Opcode {
let Types = [NumberTypeClass];
let HasGroup = 1;
}

def Shl : Opcode {
let Types = [IntegerTypeClass, IntegerTypeClass];
let HasGroup = 1;
}

def Shr : Opcode {
let Types = [IntegerTypeClass, IntegerTypeClass];
let HasGroup = 1;
}

def BitAnd : IntegerOpcode;
def BitOr : IntegerOpcode;
def Div : Opcode {
Expand Down
142 changes: 142 additions & 0 deletions clang/test/AST/Interp/shifts.cpp
@@ -0,0 +1,142 @@
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -std=c++20 -verify %s
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -std=c++17 -verify=cxx17 %s
// RUN: %clang_cc1 -std=c++20 -verify=ref %s
// RUN: %clang_cc1 -std=c++17 -verify=ref-cxx17 %s

#define INT_MIN (~__INT_MAX__)


namespace shifts {
constexpr void test() { // ref-error {{constexpr function never produces a constant expression}} \
// ref-cxx17-error {{constexpr function never produces a constant expression}}

char c; // cxx17-warning {{uninitialized variable}} \
// ref-cxx17-warning {{uninitialized variable}}

c = 0 << 0;
c = 0 << 1;
c = 1 << 0;
c = 1 << -0;
c = 1 >> -0;
c = 1 << -1; // expected-warning {{shift count is negative}} \
// cxx17-warning {{shift count is negative}} \
// ref-warning {{shift count is negative}} \
// ref-note {{negative shift count -1}} \
// ref-cxx17-warning {{shift count is negative}} \
// ref-cxx17-note {{negative shift count -1}}

c = 1 >> -1; // expected-warning {{shift count is negative}} \
// cxx17-warning {{shift count is negative}} \
// ref-warning {{shift count is negative}} \
// ref-cxx17-warning {{shift count is negative}}
c = 1 << (unsigned)-1; // expected-warning {{shift count >= width of type}} \
// FIXME: 'implicit conversion' warning missing in the new interpreter. \
// cxx17-warning {{shift count >= width of type}} \
// ref-warning {{shift count >= width of type}} \
// ref-warning {{implicit conversion}} \
// ref-cxx17-warning {{shift count >= width of type}} \
// ref-cxx17-warning {{implicit conversion}}
c = 1 >> (unsigned)-1; // expected-warning {{shift count >= width of type}} \
// cxx17-warning {{shift count >= width of type}} \
// ref-warning {{shift count >= width of type}} \
// ref-cxx17-warning {{shift count >= width of type}}
c = 1 << c;
c <<= 0;
c >>= 0;
c <<= 1;
c >>= 1;
c <<= -1; // expected-warning {{shift count is negative}} \
// cxx17-warning {{shift count is negative}} \
// ref-warning {{shift count is negative}} \
// ref-cxx17-warning {{shift count is negative}}
c >>= -1; // expected-warning {{shift count is negative}} \
// cxx17-warning {{shift count is negative}} \
// ref-warning {{shift count is negative}} \
// ref-cxx17-warning {{shift count is negative}}
c <<= 999999; // expected-warning {{shift count >= width of type}} \
// cxx17-warning {{shift count >= width of type}} \
// ref-warning {{shift count >= width of type}} \
// ref-cxx17-warning {{shift count >= width of type}}
c >>= 999999; // expected-warning {{shift count >= width of type}} \
// cxx17-warning {{shift count >= width of type}} \
// ref-warning {{shift count >= width of type}} \
// ref-cxx17-warning {{shift count >= width of type}}
c <<= __CHAR_BIT__; // expected-warning {{shift count >= width of type}} \
// cxx17-warning {{shift count >= width of type}} \
// ref-warning {{shift count >= width of type}} \
// ref-cxx17-warning {{shift count >= width of type}}
c >>= __CHAR_BIT__; // expected-warning {{shift count >= width of type}} \
// cxx17-warning {{shift count >= width of type}} \
// ref-warning {{shift count >= width of type}} \
// ref-cxx17-warning {{shift count >= width of type}}
c <<= __CHAR_BIT__+1; // expected-warning {{shift count >= width of type}} \
// cxx17-warning {{shift count >= width of type}} \
// ref-warning {{shift count >= width of type}} \
// ref-cxx17-warning {{shift count >= width of type}}
c >>= __CHAR_BIT__+1; // expected-warning {{shift count >= width of type}} \
// cxx17-warning {{shift count >= width of type}} \
// ref-warning {{shift count >= width of type}} \
// ref-cxx17-warning {{shift count >= width of type}}
(void)((long)c << __CHAR_BIT__);

int i; // cxx17-warning {{uninitialized variable}} \
// ref-cxx17-warning {{uninitialized variable}}
i = 1 << (__INT_WIDTH__ - 2);
i = 2 << (__INT_WIDTH__ - 1); // cxx17-warning {{bits to represent, but 'int' only has}} \
// ref-cxx17-warning {{bits to represent, but 'int' only has}}
i = 1 << (__INT_WIDTH__ - 1); // cxx17-warning-not {{sets the sign bit of the shift expression}}
i = -1 << (__INT_WIDTH__ - 1); // cxx17-warning {{shifting a negative signed value is undefined}} \
// ref-cxx17-warning {{shifting a negative signed value is undefined}}
i = -1 << 0; // cxx17-warning {{shifting a negative signed value is undefined}} \
// ref-cxx17-warning {{shifting a negative signed value is undefined}}
i = 0 << (__INT_WIDTH__ - 1);
i = (char)1 << (__INT_WIDTH__ - 2);

unsigned u; // cxx17-warning {{uninitialized variable}} \
// ref-cxx17-warning {{uninitialized variable}}
u = 1U << (__INT_WIDTH__ - 1);
u = 5U << (__INT_WIDTH__ - 1);

long long int lli; // cxx17-warning {{uninitialized variable}} \
// ref-cxx17-warning {{uninitialized variable}}
lli = INT_MIN << 2; // cxx17-warning {{shifting a negative signed value is undefined}} \
// ref-cxx17-warning {{shifting a negative signed value is undefined}}
lli = 1LL << (sizeof(long long) * __CHAR_BIT__ - 2);
}

static_assert(1 << 4 == 16, "");
constexpr unsigned m = 2 >> 1;
static_assert(m == 1, "");
constexpr unsigned char c = 0 << 8;
static_assert(c == 0, "");
static_assert(true << 1, "");
static_assert(1 << (__INT_WIDTH__ +1) == 0, ""); // expected-error {{not an integral constant expression}} \
// expected-note {{>= width of type 'int'}} \
// cxx17-error {{not an integral constant expression}} \
// cxx17-note {{>= width of type 'int'}} \
// ref-error {{not an integral constant expression}} \
// ref-note {{>= width of type 'int'}} \
// ref-cxx17-error {{not an integral constant expression}} \
// ref-cxx17-note {{>= width of type 'int'}}

constexpr int i1 = 1 << -1; // expected-error {{must be initialized by a constant expression}} \
// expected-note {{negative shift count -1}} \
// cxx17-error {{must be initialized by a constant expression}} \
// cxx17-note {{negative shift count -1}} \
// ref-error {{must be initialized by a constant expression}} \
// ref-note {{negative shift count -1}} \
// ref-cxx17-error {{must be initialized by a constant expression}} \
// ref-cxx17-note {{negative shift count -1}}

constexpr int i2 = 1 << (__INT_WIDTH__ + 1); // expected-error {{must be initialized by a constant expression}} \
// expected-note {{>= width of type}} \
// cxx17-error {{must be initialized by a constant expression}} \
// cxx17-note {{>= width of type}} \
// ref-error {{must be initialized by a constant expression}} \
// ref-note {{>= width of type}} \
// ref-cxx17-error {{must be initialized by a constant expression}} \
// ref-cxx17-note {{>= width of type}}

constexpr char c2 = 1;
constexpr int i3 = c2 << (__CHAR_BIT__ + 1); // Not ill-formed
};

0 comments on commit 6d965c9

Please sign in to comment.