Skip to content

[clang][bytecode] Implement constexpr vector unary operators +, -, ~, ! #105996

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

Merged
merged 7 commits into from
Aug 28, 2024
Merged
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
106 changes: 106 additions & 0 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4991,6 +4991,8 @@ bool Compiler<Emitter>::VisitUnaryOperator(const UnaryOperator *E) {
const Expr *SubExpr = E->getSubExpr();
if (SubExpr->getType()->isAnyComplexType())
return this->VisitComplexUnaryOperator(E);
if (SubExpr->getType()->isVectorType())
return this->VisitVectorUnaryOperator(E);
std::optional<PrimType> T = classify(SubExpr->getType());

switch (E->getOpcode()) {
Expand Down Expand Up @@ -5312,6 +5314,110 @@ bool Compiler<Emitter>::VisitComplexUnaryOperator(const UnaryOperator *E) {
return true;
}

template <class Emitter>
bool Compiler<Emitter>::VisitVectorUnaryOperator(const UnaryOperator *E) {
const Expr *SubExpr = E->getSubExpr();
assert(SubExpr->getType()->isVectorType());

if (DiscardResult)
return this->discard(SubExpr);

auto UnaryOp = E->getOpcode();
if (UnaryOp != UO_Plus && UnaryOp != UO_Minus && UnaryOp != UO_LNot &&
UnaryOp != UO_Not)
return this->emitInvalid(E);

// Nothing to do here.
if (UnaryOp == UO_Plus)
return this->delegate(SubExpr);

if (!Initializing) {
std::optional<unsigned> LocalIndex = allocateLocal(SubExpr);
if (!LocalIndex)
return false;
if (!this->emitGetPtrLocal(*LocalIndex, E))
return false;
}

// The offset of the temporary, if we created one.
unsigned SubExprOffset =
this->allocateLocalPrimitive(SubExpr, PT_Ptr, true, false);
if (!this->visit(SubExpr))
return false;
if (!this->emitSetLocal(PT_Ptr, SubExprOffset, E))
return false;

const auto *VecTy = SubExpr->getType()->getAs<VectorType>();
PrimType ElemT = classifyVectorElementType(SubExpr->getType());
auto getElem = [=](unsigned Offset, unsigned Index) -> bool {
if (!this->emitGetLocal(PT_Ptr, Offset, E))
return false;
return this->emitArrayElemPop(ElemT, Index, E);
};

switch (UnaryOp) {
case UO_Minus:
for (unsigned I = 0; I != VecTy->getNumElements(); ++I) {
if (!getElem(SubExprOffset, I))
return false;
if (!this->emitNeg(ElemT, E))
return false;
if (!this->emitInitElem(ElemT, I, E))
return false;
}
break;
case UO_LNot: { // !x
// In C++, the logic operators !, &&, || are available for vectors. !v is
// equivalent to v == 0.
//
// The result of the comparison is a vector of the same width and number of
// elements as the comparison operands with a signed integral element type.
//
// https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html
QualType ResultVecTy = E->getType();
PrimType ResultVecElemT =
classifyPrim(ResultVecTy->getAs<VectorType>()->getElementType());
for (unsigned I = 0; I != VecTy->getNumElements(); ++I) {
if (!getElem(SubExprOffset, I))
return false;
// operator ! on vectors returns -1 for 'truth', so negate it.
if (!this->emitPrimCast(ElemT, PT_Bool, Ctx.getASTContext().BoolTy, E))
return false;
if (!this->emitInv(E))
return false;
if (!this->emitPrimCast(PT_Bool, ElemT, VecTy->getElementType(), E))
return false;
if (!this->emitNeg(ElemT, E))
return false;
if (ElemT != ResultVecElemT &&
!this->emitPrimCast(ElemT, ResultVecElemT, ResultVecTy, E))
return false;
if (!this->emitInitElem(ResultVecElemT, I, E))
return false;
}
break;
}
case UO_Not: // ~x
for (unsigned I = 0; I != VecTy->getNumElements(); ++I) {
if (!getElem(SubExprOffset, I))
return false;
if (ElemT == PT_Bool) {
if (!this->emitInv(E))
return false;
} else {
if (!this->emitComp(ElemT, E))
return false;
}
if (!this->emitInitElem(ElemT, I, E))
return false;
}
break;
default:
llvm_unreachable("Unsupported unary operators should be handled up front");
}
return true;
}

template <class Emitter>
bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) {
if (DiscardResult)
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/AST/ByteCode/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
bool VisitGNUNullExpr(const GNUNullExpr *E);
bool VisitCXXThisExpr(const CXXThisExpr *E);
bool VisitUnaryOperator(const UnaryOperator *E);
bool VisitVectorUnaryOperator(const UnaryOperator *E);
bool VisitComplexUnaryOperator(const UnaryOperator *E);
bool VisitDeclRefExpr(const DeclRefExpr *E);
bool VisitImplicitValueInitExpr(const ImplicitValueInitExpr *E);
Expand Down Expand Up @@ -349,6 +350,11 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
return *this->classify(ElemType);
}

PrimType classifyVectorElementType(QualType T) const {
assert(T->isVectorType());
return *this->classify(T->getAs<VectorType>()->getElementType());
}

bool emitComplexReal(const Expr *SubExpr);
bool emitComplexBoolCast(const Expr *E);
bool emitComplexComparison(const Expr *LHS, const Expr *RHS,
Expand Down
90 changes: 90 additions & 0 deletions clang/test/AST/ByteCode/constexpr-vectors.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// RUN: %clang_cc1 %s -triple x86_64-linux-gnu -std=c++14 -fsyntax-only -verify
// RUN: %clang_cc1 %s -triple x86_64-linux-gnu -fexperimental-new-constant-interpreter -std=c++14 -fsyntax-only -verify

using FourCharsVecSize __attribute__((vector_size(4))) = char;
using FourIntsVecSize __attribute__((vector_size(16))) = int;
using FourLongLongsVecSize __attribute__((vector_size(32))) = long long;
using FourFloatsVecSize __attribute__((vector_size(16))) = float;
using FourDoublesVecSize __attribute__((vector_size(32))) = double;
using FourI128VecSize __attribute__((vector_size(64))) = __int128;

using FourCharsExtVec __attribute__((ext_vector_type(4))) = char;
using FourIntsExtVec __attribute__((ext_vector_type(4))) = int;
using FourI128ExtVec __attribute__((ext_vector_type(4))) = __int128;

// Only int vs float makes a difference here, so we only need to test 1 of each.
// Test Char to make sure the mixed-nature of shifts around char is evident.
void CharUsage() {
constexpr auto H = FourCharsVecSize{-1, -1, 0, -1};
constexpr auto InvH = -H;
static_assert(InvH[0] == 1 && InvH[1] == 1 && InvH[2] == 0 && InvH[3] == 1, "");

constexpr auto ae = ~FourCharsVecSize{1, 2, 10, 20};
static_assert(ae[0] == -2 && ae[1] == -3 && ae[2] == -11 && ae[3] == -21, "");

constexpr auto af = !FourCharsVecSize{0, 1, 8, -1};
static_assert(af[0] == -1 && af[1] == 0 && af[2] == 0 && af[3] == 0, "");
}

void CharExtVecUsage() {
constexpr auto H = FourCharsExtVec{-1, -1, 0, -1};
constexpr auto InvH = -H;
static_assert(InvH[0] == 1 && InvH[1] == 1 && InvH[2] == 0 && InvH[3] == 1, "");

constexpr auto ae = ~FourCharsExtVec{1, 2, 10, 20};
static_assert(ae[0] == -2 && ae[1] == -3 && ae[2] == -11 && ae[3] == -21, "");

constexpr auto af = !FourCharsExtVec{0, 1, 8, -1};
static_assert(af[0] == -1 && af[1] == 0 && af[2] == 0 && af[3] == 0, "");
}

void FloatUsage() {
constexpr auto Y = FourFloatsVecSize{1.200000e+01, 1.700000e+01, -1.000000e+00, -1.000000e+00};
constexpr auto Z = -Y;
static_assert(Z[0] == -1.200000e+01 && Z[1] == -1.700000e+01 && Z[2] == 1.000000e+00 && Z[3] == 1.000000e+00, "");

// Operator ~ is illegal on floats.
constexpr auto ae = ~FourFloatsVecSize{0, 1, 8, -1}; // expected-error {{invalid argument type}}

constexpr auto af = !FourFloatsVecSize{0, 1, 8, -1};
static_assert(af[0] == -1 && af[1] == 0 && af[2] == 0 && af[3] == 0, "");
}

void FloatVecUsage() {
constexpr auto Y = FourFloatsVecSize{1.200000e+01, 1.700000e+01, -1.000000e+00, -1.000000e+00};
constexpr auto Z = -Y;
static_assert(Z[0] == -1.200000e+01 && Z[1] == -1.700000e+01 && Z[2] == 1.000000e+00 && Z[3] == 1.000000e+00, "");

// Operator ~ is illegal on floats.
constexpr auto ae = ~FourFloatsVecSize{0, 1, 8, -1}; // expected-error {{invalid argument type}}

constexpr auto af = !FourFloatsVecSize{0, 1, 8, -1};
static_assert(af[0] == -1 && af[1] == 0 && af[2] == 0 && af[3] == 0, "");
}

void I128Usage() {
// Operator ~ is illegal on floats, so no test for that.
constexpr auto c = ~FourI128VecSize{1, 2, 10, 20};
static_assert(c[0] == -2 && c[1] == -3 && c[2] == -11 && c[3] == -21, "");

constexpr auto d = !FourI128VecSize{0, 1, 8, -1};
static_assert(d[0] == -1 && d[1] == 0 && d[2] == 0 && d[3] == 0, "");
}

void I128VecUsage() {
// Operator ~ is illegal on floats, so no test for that.
constexpr auto c = ~FourI128ExtVec{1, 2, 10, 20};
static_assert(c[0] == -2 && c[1] == -3 && c[2] == -11 && c[3] == -21, "");

constexpr auto d = !FourI128ExtVec{0, 1, 8, -1};
static_assert(d[0] == -1 && d[1] == 0 && d[2] == 0 && d[3] == 0, "");
}

using FourBoolsExtVec __attribute__((ext_vector_type(4))) = bool;
void BoolVecUsage() {
constexpr auto j = !FourBoolsExtVec{true, false, true, false};
static_assert(j[0] == false && j[1] == true && j[2] == false && j[3] == true, "");

constexpr auto k = ~FourBoolsExtVec{true, false, true, false};
static_assert(k[0] == false && k[1] == true && k[2] == false && k[3] == true, "");
}
Loading