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 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
11 changes: 11 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3798,6 +3798,17 @@ Trivially relocates ``count`` objects of relocatable, complete type ``T``
from ``src`` to ``dest`` and returns ``dest``.
This builtin is used to implement ``std::trivially_relocate``.

``__builtin_invoke``
--------------------

**Syntax**:

.. code-block:: c++

template <class Callee, class... Args>
decltype(auto) __builtin_invoke(Callee&& callee, Args&&... args);

``__builtin_invoke`` is equivalent to ``std::invoke``.

``__builtin_preserve_access_index``
-----------------------------------
Expand Down
5 changes: 3 additions & 2 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ Non-comprehensive list of changes in this release
different than before.
- Fixed a crash when a VLA with an invalid size expression was used within a
``sizeof`` or ``typeof`` expression. (#GH138444)
- ``__builtin_invoke`` has been added to improve the compile time of ``std::invoke``.
- Deprecation warning is emitted for the deprecated ``__reference_binds_to_temporary`` intrinsic.
``__reference_constructs_from_temporary`` should be used instead. (#GH44056)
- Added `__builtin_get_vtable_pointer` to directly load the primary vtable pointer from a
Expand Down Expand Up @@ -656,7 +657,7 @@ Improvements to Clang's diagnostics
false positives in exception-heavy code, though only simple patterns
are currently recognized.


Improvements to Clang's time-trace
----------------------------------

Expand Down Expand Up @@ -734,7 +735,7 @@ Bug Fixes in This Version
- Fixed incorrect token location when emitting diagnostics for tokens expanded from macros. (#GH143216)
- Fixed an infinite recursion when checking constexpr destructors. (#GH141789)
- Fixed a crash when a malformed using declaration appears in a ``constexpr`` function. (#GH144264)
- Fixed a bug when use unicode character name in macro concatenation. (#GH145240)
- Fixed a bug when use unicode character name in macro concatenation. (#GH145240)

Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -4314,6 +4314,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
7 changes: 7 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -15192,11 +15192,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
95 changes: 95 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2261,6 +2261,99 @@ static bool BuiltinCountZeroBitsGeneric(Sema &S, CallExpr *TheCall) {
return false;
}

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

if (Args.size() == 0) {
S.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();
}

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

if (const auto *MPT = FuncT->getAs<MemberPointerType>()) {
if (Args.size() < 2) {
S.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();
}

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

if (MPT->isMemberDataPointer() && S.checkArgCount(TheCall, 2))
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 (S.Context.hasSameType(QualType(MemPtrClass, 0),
S.BuiltinRemoveCVRef(ObjectT, Loc)) ||
S.BuiltinIsBaseOf(Args[1]->getBeginLoc(), QualType(MemPtrClass, 0),
S.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 (const auto *RD = ObjectT->getAsCXXRecordDecl()) {
if (RD->isInStdNamespace() &&
RD->getDeclName().getAsString() == "reference_wrapper") {
Comment on lines +2312 to +2313
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not opposed, but I'm also not delighted for more special knowledge about STL interfaces leaking into the compiler implementation.

CXXScopeSpec SS;
IdentifierInfo *GetName = &S.Context.Idents.get("get");
UnqualifiedId GetID;
GetID.setIdentifier(GetName, Loc);

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

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

return S.ActOnCallExpr(S.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 S.ActOnUnaryOp(S.getCurScope(), Loc, tok::star, Args[1]);
}();

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

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

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

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

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

ExprResult
Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
CallExpr *TheCall) {
Expand Down Expand Up @@ -2420,6 +2513,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(*this, TheCall);
case Builtin::BI__builtin_prefetch:
if (BuiltinPrefetch(TheCall))
return ExprError();
Expand Down
105 changes: 54 additions & 51 deletions clang/lib/Sema/SemaTypeTraits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,58 @@ ExprResult Sema::ActOnTypeTrait(TypeTrait Kind, SourceLocation KWLoc,
return BuildTypeTrait(Kind, KWLoc, ConvertedArgs, RParenLoc);
}

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;

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

if (RequireCompleteType(RhsTLoc, RhsT,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this diagnostic is tested.

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 added a test for the case below. If anybody can inform me how to create an ObjCObjectType I'm happy to add a test for this as well (though I'm not sure testing this is that useful).

diag::err_incomplete_type_used_in_type_trait_expr))
return false;

return BaseInterface->isSuperClassOf(DerivedInterface);
}

assert(Context.hasSameUnqualifiedType(LhsT, RhsT) ==
(lhsRecord == rhsRecord));

// 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;

// 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;

return cast<CXXRecordDecl>(rhsRecord->getDecl())
->isDerivedFrom(cast<CXXRecordDecl>(lhsRecord->getDecl()));
}

static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT,
const TypeSourceInfo *Lhs,
const TypeSourceInfo *Rhs,
Expand All @@ -1590,58 +1642,9 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT,
"Cannot evaluate traits of dependent types");

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;

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

if (Self.RequireCompleteType(
Rhs->getTypeLoc().getBeginLoc(), RhsT,
diag::err_incomplete_type_used_in_type_trait_expr))
return false;

return BaseInterface->isSuperClassOf(DerivedInterface);
}

assert(Self.Context.hasSameUnqualifiedType(LhsT, RhsT) ==
(lhsRecord == rhsRecord));

// 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;
case BTT_IsBaseOf:
return Self.BuiltinIsBaseOf(Rhs->getTypeLoc().getBeginLoc(), LhsT, RhsT);

if (lhsRecord == rhsRecord)
return true;

// 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;

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