Skip to content

[clang][Interp] Handle std::move etc. builtins #70772

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 1 commit into from
Jan 31, 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
9 changes: 1 addition & 8 deletions clang/lib/AST/Interp/ByteCodeExprGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,8 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
// If the function does not exist yet, it is compiled.
const Function *getFunction(const FunctionDecl *FD);

/// Classifies a type.
std::optional<PrimType> classify(const Expr *E) const {
if (E->isGLValue()) {
if (E->getType()->isFunctionType())
return PT_FnPtr;
return PT_Ptr;
}

return classify(E->getType());
return Ctx.classify(E);
}
std::optional<PrimType> classify(QualType Ty) const {
return Ctx.classify(Ty);
Expand Down
13 changes: 12 additions & 1 deletion clang/lib/AST/Interp/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,20 @@ class Context final {
/// Return the size of T in bits.
uint32_t getBitWidth(QualType T) const { return Ctx.getIntWidth(T); }

/// Classifies an expression.
/// Classifies a type.
std::optional<PrimType> classify(QualType T) const;

/// Classifies an expression.
std::optional<PrimType> classify(const Expr *E) const {
if (E->isGLValue()) {
if (E->getType()->isFunctionType())
return PT_FnPtr;
return PT_Ptr;
}

return classify(E->getType());
}

const CXXMethodDecl *
getOverridingFunction(const CXXRecordDecl *DynamicDecl,
const CXXRecordDecl *StaticDecl,
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/Interp/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ static bool CheckGlobal(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
namespace clang {
namespace interp {
static void popArg(InterpState &S, const Expr *Arg) {
PrimType Ty = S.getContext().classify(Arg->getType()).value_or(PT_Ptr);
PrimType Ty = S.getContext().classify(Arg).value_or(PT_Ptr);
TYPE_SWITCH(Ty, S.Stk.discard<T>());
}

Expand Down
22 changes: 21 additions & 1 deletion clang/lib/AST/Interp/InterpBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -634,12 +634,23 @@ static bool interp__builtin_addressof(InterpState &S, CodePtr OpPC,
return true;
}

static bool interp__builtin_move(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame, const Function *Func,
const CallExpr *Call) {

PrimType ArgT = S.getContext().classify(Call->getArg(0)).value_or(PT_Ptr);

TYPE_SWITCH(ArgT, const T &Arg = S.Stk.peek<T>(); S.Stk.push<T>(Arg););

return Func->getDecl()->isConstexpr();
}

bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
const CallExpr *Call) {
InterpFrame *Frame = S.Current;
APValue Dummy;

std::optional<PrimType> ReturnT = S.getContext().classify(Call->getType());
std::optional<PrimType> ReturnT = S.getContext().classify(Call);

// If classify failed, we assume void.
assert(ReturnT || Call->getType()->isVoidType());
Expand Down Expand Up @@ -848,6 +859,15 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
return false;
break;

case Builtin::BIas_const:
case Builtin::BIforward:
case Builtin::BIforward_like:
case Builtin::BImove:
case Builtin::BImove_if_noexcept:
if (!interp__builtin_move(S, OpPC, Frame, F, Call))
return false;
break;

default:
return false;
}
Expand Down
89 changes: 89 additions & 0 deletions clang/test/AST/Interp/functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,92 @@ namespace AddressOf {
constexpr _Complex float F = {3, 4};
static_assert(__builtin_addressof(F) == &F, "");
}

namespace std {
template <typename T> struct remove_reference { using type = T; };
template <typename T> struct remove_reference<T &> { using type = T; };
template <typename T> struct remove_reference<T &&> { using type = T; };
template <typename T>
constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept {
return static_cast<typename std::remove_reference<T>::type &&>(t);
}
}
/// The std::move declaration above gets translated to a builtin function.
namespace Move {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see test coverage involving a move constructor, a move assignment operator, a direct call to __builtin_move, and some testing for std::as_const and std::forward. Of special interest would be times when there's UB in the move constructor/move assignment that should be caught or a non-copyable object where move semantics are the only thing that should work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've enabled test/SemaCXX/builtin-std-move.cpp with the new interpreter.

#if __cplusplus >= 202002L
consteval int f_eval() { // expected-note 12{{declared here}} \
// ref-note 12{{declared here}}
return 0;
}

/// From test/SemaCXX/cxx2a-consteval.
struct Copy {
int(*ptr)();
constexpr Copy(int(*p)() = nullptr) : ptr(p) {}
consteval Copy(const Copy&) = default;
};

constexpr const Copy &to_lvalue_ref(const Copy &&a) {
return a;
}

void test() {
constexpr const Copy C;
// there is no the copy constructor call when its argument is a prvalue because of garanteed copy elision.
// so we need to test with both prvalue and xvalues.
{ Copy c(C); }
{ Copy c((Copy(&f_eval))); } // expected-error {{cannot take address of consteval}} \
// ref-error {{cannot take address of consteval}}
{ Copy c(std::move(C)); }
{ Copy c(std::move(Copy(&f_eval))); } // expected-error {{is not a constant expression}} \
// expected-note {{to a consteval}} \
// ref-error {{is not a constant expression}} \
// ref-note {{to a consteval}}
{ Copy c(to_lvalue_ref((Copy(&f_eval)))); } // expected-error {{is not a constant expression}} \
// expected-note {{to a consteval}} \
// ref-error {{is not a constant expression}} \
// ref-note {{to a consteval}}
{ Copy c(to_lvalue_ref(std::move(C))); }
{ Copy c(to_lvalue_ref(std::move(Copy(&f_eval)))); } // expected-error {{is not a constant expression}} \
// expected-note {{to a consteval}} \
// ref-error {{is not a constant expression}} \
// ref-note {{to a consteval}}
{ Copy c = Copy(C); }
{ Copy c = Copy(Copy(&f_eval)); } // expected-error {{cannot take address of consteval}} \
// ref-error {{cannot take address of consteval}}
{ Copy c = Copy(std::move(C)); }
{ Copy c = Copy(std::move(Copy(&f_eval))); } // expected-error {{is not a constant expression}} \
// expected-note {{to a consteval}} \
// ref-error {{is not a constant expression}} \
// ref-note {{to a consteval}}
{ Copy c = Copy(to_lvalue_ref(Copy(&f_eval))); } // expected-error {{is not a constant expression}} \
// expected-note {{to a consteval}} \
// ref-error {{is not a constant expression}} \
// ref-note {{to a consteval}}
{ Copy c = Copy(to_lvalue_ref(std::move(C))); }
{ Copy c = Copy(to_lvalue_ref(std::move(Copy(&f_eval)))); } // expected-error {{is not a constant expression}} \
// expected-note {{to a consteval}} \
// ref-error {{is not a constant expression}} \
// ref-note {{to a consteval}}
{ Copy c; c = Copy(C); }
{ Copy c; c = Copy(Copy(&f_eval)); } // expected-error {{cannot take address of consteval}} \
// ref-error {{cannot take address of consteval}}
{ Copy c; c = Copy(std::move(C)); }
{ Copy c; c = Copy(std::move(Copy(&f_eval))); } // expected-error {{is not a constant expression}} \
// expected-note {{to a consteval}} \
// ref-error {{is not a constant expression}} \
// ref-note {{to a consteval}}
{ Copy c; c = Copy(to_lvalue_ref(Copy(&f_eval))); } // expected-error {{is not a constant expression}} \
// expected-note {{to a consteval}} \
// ref-error {{is not a constant expression}} \
// ref-note {{to a consteval}}
{ Copy c; c = Copy(to_lvalue_ref(std::move(C))); }
{ Copy c; c = Copy(to_lvalue_ref(std::move(Copy(&f_eval)))); } // expected-error {{is not a constant expression}} \
// expected-note {{to a consteval}} \
// ref-error {{is not a constant expression}} \
// ref-note {{to a consteval}}
}
#endif
constexpr int A = std::move(5);
static_assert(A == 5, "");
}
6 changes: 5 additions & 1 deletion clang/test/SemaCXX/builtin-std-move.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// RUN: %clang_cc1 -std=c++17 -verify=cxx17,expected %s
// RUN: %clang_cc1 -std=c++17 -verify=cxx17,expected %s -DNO_CONSTEXPR
// RUN: %clang_cc1 -std=c++20 -verify=cxx20,expected %s
//
// RUN: %clang_cc1 -std=c++17 -verify=cxx17,expected %s -fexperimental-new-constant-interpreter -DNEW_INTERP
// RUN: %clang_cc1 -std=c++17 -verify=cxx17,expected %s -fexperimental-new-constant-interpreter -DNEW_INTERP -DNO_CONSTEXPR
// RUN: %clang_cc1 -std=c++20 -verify=cxx20,expected %s -fexperimental-new-constant-interpreter -DNEW_INTERP

namespace std {
#ifndef NO_CONSTEXPR
Expand Down Expand Up @@ -112,7 +116,7 @@ constexpr bool f(A a) { // #f

#ifndef NO_CONSTEXPR
static_assert(f({}), "should be constexpr");
#else
#elif !defined(NEW_INTERP)
// expected-error@#f {{never produces a constant expression}}
// expected-note@#call {{}}
#endif
Expand Down