Skip to content

[Clang] Add __builtin_invoke and use it in libc++ #116709

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 17 commits into from
Jun 29, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -4272,6 +4272,12 @@ def MoveIfNsoexcept : CxxLibBuiltin<"utility"> {
let Namespace = "std";
}

def Invoke : Builtin {
let Spellings = ["__builtin_invoke"];
let Attributes = [CustomTypeChecking, Constexpr];
let Prototype = "void(...)";
}

def Annotation : Builtin {
let Spellings = ["__builtin_annotation"];
let Attributes = [NoThrow, CustomTypeChecking];
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -2594,6 +2594,8 @@ class Sema final : public SemaBase {
SourceLocation BuiltinLoc,
SourceLocation RParenLoc);

ExprResult BuiltinInvoke(CallExpr *TheCall);

static StringRef GetFormatStringTypeName(FormatStringType FST);
static FormatStringType GetFormatStringType(StringRef FormatFlavor);
static FormatStringType GetFormatStringType(const FormatAttr *Format);
Expand Down Expand Up @@ -15220,11 +15222,18 @@ class Sema final : public SemaBase {
SourceLocation Loc);
QualType BuiltinRemoveReference(QualType BaseType, UTTKind UKind,
SourceLocation Loc);

QualType BuiltinRemoveCVRef(QualType BaseType, SourceLocation Loc) {
return BuiltinRemoveReference(BaseType, UTTKind::RemoveCVRef, Loc);
}

QualType BuiltinChangeCVRQualifiers(QualType BaseType, UTTKind UKind,
SourceLocation Loc);
QualType BuiltinChangeSignedness(QualType BaseType, UTTKind UKind,
SourceLocation Loc);

bool BuiltinIsBaseOf(SourceLocation RhsTLoc, QualType LhsT, QualType RhsT);

/// Ensure that the type T is a literal type.
///
/// This routine checks whether the type @p T is a literal type. If @p T is an
Expand Down
99 changes: 99 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2368,6 +2368,8 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
return BuiltinShuffleVector(TheCall);
// TheCall will be freed by the smart pointer here, but that's fine, since
// BuiltinShuffleVector guts it, but then doesn't release it.
case Builtin::BI__builtin_invoke:
return BuiltinInvoke(TheCall);
case Builtin::BI__builtin_prefetch:
if (BuiltinPrefetch(TheCall))
return ExprError();
Expand Down Expand Up @@ -5406,6 +5408,103 @@ ExprResult Sema::ConvertVectorExpr(Expr *E, TypeSourceInfo *TInfo,
RParenLoc, CurFPFeatureOverrides());
}

ExprResult Sema::BuiltinInvoke(CallExpr *TheCall) {
SourceLocation Loc = TheCall->getBeginLoc();
auto Args = MutableArrayRef(TheCall->getArgs(), TheCall->getNumArgs());
assert(llvm::none_of(Args,
[](Expr *Arg) { return Arg->isTypeDependent(); }));

if (Args.size() == 0) {
Diag(TheCall->getBeginLoc(), diag::err_typecheck_call_too_few_args_at_least)
<< /*callee_type=*/0 << /*min_arg_count=*/1 << /*actual_arg_count=*/0
<< /*is_non_object=*/0 << TheCall->getSourceRange();
return ExprError();
}

auto FuncT = Args[0]->getType();

if (auto *MPT = FuncT->getAs<MemberPointerType>()) {
if (Args.size() < 2) {
Diag(TheCall->getBeginLoc(),
diag::err_typecheck_call_too_few_args_at_least)
<< /*callee_type=*/0 << /*min_arg_count=*/2 << /*actual_arg_count=*/1
<< /*is_non_object=*/0 << TheCall->getSourceRange();
return ExprError();
}

auto *MemPtrClass = MPT->getQualifier()->getAsType();
auto ObjectT = Args[1]->getType();


if (MPT->isMemberDataPointer() && Args.size() != 2) {
Diag(TheCall->getBeginLoc(), diag::err_typecheck_call_too_many_args)
<< 0 << 2 << Args.size() << 0 << TheCall->getSourceRange();
return ExprError();
}

ExprResult ObjectArg = [&]() -> ExprResult {
// (1.1): (t1.*f)(t2, …, tN) when f is a pointer to a member function of a
// class T and is_same_v<T, remove_cvref_t<decltype(t1)>> ||
// is_base_of_v<T, remove_cvref_t<decltype(t1)>> is true;
// (1.4): t1.*f when N=1 and f is a pointer to data member of a class T
// and is_same_v<T, remove_cvref_t<decltype(t1)>> ||
// is_base_of_v<T, remove_cvref_t<decltype(t1)>> is true;
if (Context.hasSameType(QualType(MemPtrClass, 0),
BuiltinRemoveCVRef(ObjectT, Loc)) ||
BuiltinIsBaseOf(Args[1]->getBeginLoc(), QualType(MemPtrClass, 0),
BuiltinRemoveCVRef(ObjectT, Loc))) {
return Args[1];
}

// (t1.get().*f)(t2, …, tN) when f is a pointer to a member function of
// a class T and remove_cvref_t<decltype(t1)> is a specialization of
// reference_wrapper;
if (auto *RD = ObjectT->getAsCXXRecordDecl()) {
if (RD->isInStdNamespace() &&
RD->getDeclName().getAsString() == "reference_wrapper") {
CXXScopeSpec SS;
IdentifierInfo *GetName = &Context.Idents.get("get");
UnqualifiedId GetID;
GetID.setIdentifier(GetName, Loc);

auto MemExpr = ActOnMemberAccessExpr(
getCurScope(), Args[1], Loc, tok::period, SS,
/*TemplateKWLoc=*/SourceLocation(), GetID, nullptr);

if (MemExpr.isInvalid())
return ExprError();

return ActOnCallExpr(getCurScope(), MemExpr.get(), Loc, {}, Loc);
}
}

// ((*t1).*f)(t2, …, tN) when f is a pointer to a member function of a
// class T and t1 does not satisfy the previous two items;

return ActOnUnaryOp(getCurScope(), Loc, tok::star, Args[1]);
}();

if (ObjectArg.isInvalid())
return ExprError();

auto BinOp = ActOnBinOp(getCurScope(), TheCall->getBeginLoc(),
tok::periodstar, ObjectArg.get(), Args[0]);
if (BinOp.isInvalid())
return ExprError();

if (MPT->isMemberDataPointer())
return BinOp;

auto *MemCall = new (Context)
ParenExpr(SourceLocation(), SourceLocation(), BinOp.get());

return ActOnCallExpr(getCurScope(), MemCall, TheCall->getBeginLoc(),
Args.drop_front(2), TheCall->getRParenLoc());
}
return ActOnCallExpr(getCurScope(), Args.front(), TheCall->getBeginLoc(),
Args.drop_front(), TheCall->getRParenLoc());
}

bool Sema::BuiltinPrefetch(CallExpr *TheCall) {
unsigned NumArgs = TheCall->getNumArgs();

Expand Down
105 changes: 54 additions & 51 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6540,67 +6540,70 @@ ExprResult Sema::ActOnTypeTrait(TypeTrait Kind, SourceLocation KWLoc,
return BuildTypeTrait(Kind, KWLoc, ConvertedArgs, RParenLoc);
}

static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceInfo *Lhs,
const TypeSourceInfo *Rhs, SourceLocation KeyLoc) {
QualType LhsT = Lhs->getType();
QualType RhsT = Rhs->getType();
bool Sema::BuiltinIsBaseOf(SourceLocation RhsTLoc, QualType LhsT,
QualType RhsT) {
// C++0x [meta.rel]p2
// Base is a base class of Derived without regard to cv-qualifiers or
// Base and Derived are not unions and name the same class type without
// regard to cv-qualifiers.

const RecordType *lhsRecord = LhsT->getAs<RecordType>();
const RecordType *rhsRecord = RhsT->getAs<RecordType>();
if (!rhsRecord || !lhsRecord) {
const ObjCObjectType *LHSObjTy = LhsT->getAs<ObjCObjectType>();
const ObjCObjectType *RHSObjTy = RhsT->getAs<ObjCObjectType>();
if (!LHSObjTy || !RHSObjTy)
return false;

assert(!LhsT->isDependentType() && !RhsT->isDependentType() &&
"Cannot evaluate traits of dependent types");
ObjCInterfaceDecl *BaseInterface = LHSObjTy->getInterface();
ObjCInterfaceDecl *DerivedInterface = RHSObjTy->getInterface();
if (!BaseInterface || !DerivedInterface)
return false;

switch(BTT) {
case BTT_IsBaseOf: {
// C++0x [meta.rel]p2
// Base is a base class of Derived without regard to cv-qualifiers or
// Base and Derived are not unions and name the same class type without
// regard to cv-qualifiers.

const RecordType *lhsRecord = LhsT->getAs<RecordType>();
const RecordType *rhsRecord = RhsT->getAs<RecordType>();
if (!rhsRecord || !lhsRecord) {
const ObjCObjectType *LHSObjTy = LhsT->getAs<ObjCObjectType>();
const ObjCObjectType *RHSObjTy = RhsT->getAs<ObjCObjectType>();
if (!LHSObjTy || !RHSObjTy)
return false;
if (RequireCompleteType(RhsTLoc, RhsT,
diag::err_incomplete_type_used_in_type_trait_expr))
return false;

ObjCInterfaceDecl *BaseInterface = LHSObjTy->getInterface();
ObjCInterfaceDecl *DerivedInterface = RHSObjTy->getInterface();
if (!BaseInterface || !DerivedInterface)
return false;
return BaseInterface->isSuperClassOf(DerivedInterface);
}

if (Self.RequireCompleteType(
Rhs->getTypeLoc().getBeginLoc(), RhsT,
diag::err_incomplete_type_used_in_type_trait_expr))
return false;
assert(Context.hasSameUnqualifiedType(LhsT, RhsT) ==
(lhsRecord == rhsRecord));

return BaseInterface->isSuperClassOf(DerivedInterface);
}
// Unions are never base classes, and never have base classes.
// It doesn't matter if they are complete or not. See PR#41843
if (lhsRecord && lhsRecord->getDecl()->isUnion())
return false;
if (rhsRecord && rhsRecord->getDecl()->isUnion())
return false;

if (lhsRecord == rhsRecord)
return true;

assert(Self.Context.hasSameUnqualifiedType(LhsT, RhsT)
== (lhsRecord == rhsRecord));
// C++0x [meta.rel]p2:
// If Base and Derived are class types and are different types
// (ignoring possible cv-qualifiers) then Derived shall be a
// complete type.
if (RequireCompleteType(RhsTLoc, RhsT,
diag::err_incomplete_type_used_in_type_trait_expr))
return false;

// Unions are never base classes, and never have base classes.
// It doesn't matter if they are complete or not. See PR#41843
if (lhsRecord && lhsRecord->getDecl()->isUnion())
return false;
if (rhsRecord && rhsRecord->getDecl()->isUnion())
return false;
return cast<CXXRecordDecl>(rhsRecord->getDecl())
->isDerivedFrom(cast<CXXRecordDecl>(lhsRecord->getDecl()));
}

if (lhsRecord == rhsRecord)
return true;
static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceInfo *Lhs,
Copy link
Contributor

Choose a reason for hiding this comment

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

Were these refactors necessary?

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 think so. This allows me to use them in BuiltinInvoke above and be very close to the wording in the standard. I guess it would be possible to call BuildTypeTrait instead, but that seems much less readable to me (and would require me to find a TypeSourceInfo somewhere).

const TypeSourceInfo *Rhs, SourceLocation KeyLoc) {
QualType LhsT = Lhs->getType();
QualType RhsT = Rhs->getType();

// C++0x [meta.rel]p2:
// If Base and Derived are class types and are different types
// (ignoring possible cv-qualifiers) then Derived shall be a
// complete type.
if (Self.RequireCompleteType(
Rhs->getTypeLoc().getBeginLoc(), RhsT,
diag::err_incomplete_type_used_in_type_trait_expr))
return false;
assert(!LhsT->isDependentType() && !RhsT->isDependentType() &&
"Cannot evaluate traits of dependent types");

switch(BTT) {
case BTT_IsBaseOf:
return Self.BuiltinIsBaseOf(Rhs->getTypeLoc().getBeginLoc(), LhsT, RhsT);

return cast<CXXRecordDecl>(rhsRecord->getDecl())
->isDerivedFrom(cast<CXXRecordDecl>(lhsRecord->getDecl()));
}
case BTT_IsVirtualBaseOf: {
const RecordType *BaseRecord = LhsT->getAs<RecordType>();
const RecordType *DerivedRecord = RhsT->getAs<RecordType>();
Expand Down
61 changes: 61 additions & 0 deletions clang/test/CodeGenCXX/builtin-invoke.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// RUN: %clang_cc1 -triple=x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s

extern "C" void* memcpy(void*, const void*, decltype(sizeof(int)));
void func();

namespace std {
template <class T>
class reference_wrapper {
T* ptr;

public:
T& get() { return *ptr; }
};
} // namespace std

struct Callable {
void operator()() {}

void func();
};

extern "C" void call1() {
__builtin_invoke(func);
__builtin_invoke(Callable{});
__builtin_invoke(memcpy, nullptr, nullptr, 0);

// CHECK: define dso_local void @call1
// CHECK-NEXT: entry:
// CHECK-NEXT: %ref.tmp = alloca %struct.Callable, align 1
// CHECK-NEXT: call void @_Z4funcv()
// CHECK-NEXT: call void @_ZN8CallableclEv(ptr noundef nonnull align 1 dereferenceable(1) %ref.tmp)
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 null, ptr align 1 null, i64 0, i1 false)
// CHECK-NEXT: ret void
}

extern "C" void call_memptr(std::reference_wrapper<Callable> wrapper) {
__builtin_invoke(&Callable::func, wrapper);

// CHECK: define dso_local void @call_memptr
// CHECK-NEXT: entry:
// CHECK-NEXT: %wrapper = alloca %"class.std::reference_wrapper", align 8
// CHECK-NEXT: %coerce.dive = getelementptr inbounds nuw %"class.std::reference_wrapper", ptr %wrapper, i32 0, i32 0
// CHECK-NEXT: store ptr %wrapper.coerce, ptr %coerce.dive, align 8
// CHECK-NEXT: %call = call noundef nonnull align 1 dereferenceable(1) ptr @_ZNSt17reference_wrapperI8CallableE3getEv(ptr noundef nonnull align 8 dereferenceable(8) %wrapper)
// CHECK-NEXT: %0 = getelementptr inbounds i8, ptr %call, i64 0
// CHECK-NEXT: br i1 false, label %memptr.virtual, label %memptr.nonvirtual
// CHECK-EMPTY:
// CHECK-NEXT: memptr.virtual:
// CHECK-NEXT: %vtable = load ptr, ptr %0, align 8
// CHECK-NEXT: %1 = getelementptr i8, ptr %vtable, i64 sub (i64 ptrtoint (ptr @_ZN8Callable4funcEv to i64), i64 1), !nosanitize !2
// CHECK-NEXT: %memptr.virtualfn = load ptr, ptr %1, align 8, !nosanitize !2
// CHECK-NEXT: br label %memptr.end
// CHECK-EMPTY:
// CHECK-NEXT: memptr.nonvirtual:
// CHECK-NEXT: br label %memptr.end
// CHECK-EMPTY:
// CHECK-NEXT: memptr.end:
// CHECK-NEXT: %2 = phi ptr [ %memptr.virtualfn, %memptr.virtual ], [ @_ZN8Callable4funcEv, %memptr.nonvirtual ]
// CHECK-NEXT: call void %2(ptr noundef nonnull align 1 dereferenceable(1) %0)
// CHECK-NEXT: ret void
}
Loading
Loading