Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -1197,6 +1197,14 @@ let Documentation = [Undocumented]; } +def Callback : InheritableAttr { + let Spellings = [Clang<"callback">]; + let Args = [UnsignedArgument<"CalleeIdx">, + VariadicUnsignedArgument<"PayloadIndices">]; + let Subjects = SubjectList<[Function]>; + let Documentation = [Undocumented]; +} + def GNUInline : InheritableAttr { let Spellings = [GCC<"gnu_inline">]; let Subjects = SubjectList<[Function]>; Index: include/clang/Basic/Builtins.h =================================================================== --- include/clang/Basic/Builtins.h +++ include/clang/Basic/Builtins.h @@ -194,6 +194,12 @@ /// argument and whether this function as a va_list argument. bool isScanfLike(unsigned ID, unsigned &FormatIdx, bool &HasVAListArg); + /// Determine whether this builtin has callback behavior (see + /// llvm::AbstractCallSites for details). If so, set the index to the + /// callback callee argument and the callback payload argument. + bool performsCallback(unsigned ID, unsigned &CalleeIdx, + unsigned &PayloadIdx) const; + /// Return true if this function has no side effects and doesn't /// read memory, except for possibly errno. /// Index: include/clang/Basic/Builtins.def =================================================================== --- include/clang/Basic/Builtins.def +++ include/clang/Basic/Builtins.def @@ -93,6 +93,7 @@ // j -> returns_twice (like setjmp) // u -> arguments are not evaluated for their side-effects // V:N: -> requires vectors of at least N bits to be legal +// C:N:M: -> callback behavior: argument N is called with argument M as payload // FIXME: gcc has nonnull #if defined(BUILTIN) && !defined(LIBBUILTIN) @@ -956,6 +957,9 @@ // POSIX unistd.h LIBBUILTIN(_exit, "vi", "fr", "unistd.h", ALL_GNU_LANGUAGES) LIBBUILTIN(vfork, "p", "fj", "unistd.h", ALL_LANGUAGES) +// POSIX pthread.h +LIBBUILTIN(pthread_create, "iv*vC*v*v*", "fC:3:4:", "pthread.h", ALL_GNU_LANGUAGES) + // POSIX setjmp.h LIBBUILTIN(_setjmp, "iJ", "fj", "setjmp.h", ALL_LANGUAGES) Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -2588,6 +2588,10 @@ def err_format_attribute_implicit_this_format_string : Error< "format attribute cannot specify the implicit this argument as the format " "string">; +def err_callback_attribute_no_callee : Error< + "no callback callee argument index specified">; +def err_callback_attribute_multiple : Error< + "multiple callback attributes found">; def err_init_method_bad_return_type : Error< "init methods must return an object pointer type, not %0">; def err_attribute_invalid_size : Error< Index: lib/Basic/Builtins.cpp =================================================================== --- lib/Basic/Builtins.cpp +++ lib/Basic/Builtins.cpp @@ -156,6 +156,29 @@ return isLike(ID, FormatIdx, HasVAListArg, "sS"); } +bool Builtin::Context::performsCallback(unsigned ID, unsigned &CalleeIdx, + unsigned &PayloadIdx) const { + const char *CalleePos = ::strchr(getRecord(ID).Attributes, 'C'); + if (!CalleePos) + return false; + + ++CalleePos; + assert(*CalleePos == ':' && + "Callback callee specifier must be followed by a ':'"); + ++CalleePos; + + char *EndPos; + CalleeIdx = ::strtol(CalleePos, &EndPos, 10); + assert(*EndPos == ':' && "Callback callee specifier must end with a ':'"); + + CalleePos = EndPos + 1; + + PayloadIdx = ::strtol(CalleePos, &EndPos, 10); + assert(*EndPos == ':' && "Callback payload specifier must end with a ':'"); + + return true; +} + bool Builtin::Context::canBeRedeclared(unsigned ID) const { return ID == Builtin::NotBuiltin || ID == Builtin::BI__va_start || Index: lib/CodeGen/CGOpenMPRuntime.cpp =================================================================== --- lib/CodeGen/CGOpenMPRuntime.cpp +++ lib/CodeGen/CGOpenMPRuntime.cpp @@ -1674,6 +1674,18 @@ auto *FnTy = llvm::FunctionType::get(CGM.VoidTy, TypeParams, /*isVarArg*/ true); RTLFn = CGM.CreateRuntimeFunction(FnTy, "__kmpc_fork_call"); + if (auto *F = dyn_cast(RTLFn)) { + if (!F->hasMetadata(llvm::LLVMContext::MD_callback)) { + llvm::MDBuilder MDB(F->getContext()); + // Annotate the callback behavior of the __kmpc_fork_call: + // - The callback callee is argument number 2 (microtask). + // - The first two arguments of the callback callee are unknown (-1). + // - All variadic arguments to the __kmpc_fork_call are passed to the + // callback callee. + F->addMetadata(llvm::LLVMContext::MD_callback, + *MDB.createCallback(3, {0, 0}, /* VarArg */ true)); + } + } break; } case OMPRTL__kmpc_global_thread_num: { @@ -2081,6 +2093,18 @@ auto *FnTy = llvm::FunctionType::get(CGM.VoidTy, TypeParams, /*isVarArg*/ true); RTLFn = CGM.CreateRuntimeFunction(FnTy, "__kmpc_fork_teams"); + if (auto *F = dyn_cast(RTLFn)) { + if (!F->hasMetadata(llvm::LLVMContext::MD_callback)) { + llvm::MDBuilder MDB(F->getContext()); + // Annotate the callback behavior of the __kmpc_fork_teams: + // - The callback callee is argument number 2 (microtask). + // - The first two arguments of the callback callee are unknown (-1). + // - All variadic arguments to the __kmpc_fork_teams are passed to the + // callback callee. + F->addMetadata(llvm::LLVMContext::MD_callback, + *MDB.createCallback(3, {0, 0}, /* VarArg */ true)); + } + } break; } case OMPRTL__kmpc_taskloop: { Index: lib/CodeGen/CodeGenModule.cpp =================================================================== --- lib/CodeGen/CodeGenModule.cpp +++ lib/CodeGen/CodeGenModule.cpp @@ -1600,6 +1600,18 @@ if (getLangOpts().OpenMP && FD->hasAttr()) getOpenMPRuntime().emitDeclareSimdFunction(FD, F); + + if (const auto *CB = FD->getAttr()) { + // Annotate the callback behavior as metadata: + // - The callback callee (as argument number). + // - The callback payloads (as argument numbers). + llvm::MDBuilder MDB(F->getContext()); + ArrayRef PayloadIndices(CB->payloadIndices_begin(), + CB->payloadIndices_size()); + F->addMetadata(llvm::LLVMContext::MD_callback, + *MDB.createCallback(CB->getCalleeIdx(), PayloadIndices, + /* VarArg */ false)); + } } void CodeGenModule::addUsedGlobal(llvm::GlobalValue *GV) { Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -13578,6 +13578,17 @@ FD->getLocation())); } + // Automatically recognized callbacks are currently limited to a single + // payload argument. + unsigned CallbackCalleeIdx, CallbackPayloadIdx; + if (Context.BuiltinInfo.performsCallback(BuiltinID, CallbackCalleeIdx, + CallbackPayloadIdx)) { + if (!FD->hasAttr()) + FD->addAttr(CallbackAttr::CreateImplicit(Context, CallbackCalleeIdx, + &CallbackPayloadIdx, 1, + FD->getLocation())); + } + // Mark const if we don't care about errno and that is the only thing // preventing the function from being const. This allows IRgen to use LLVM // intrinsics for such functions. Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -3455,6 +3455,64 @@ D->addAttr(NewAttr); } +/// Handle __attribute__((callback(CalleeIdx, PayloadIdx0, ...))) attributes. +static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + // In C++ the implicit 'this' function parameter also counts, and they are + // counted from one. + bool HasImplicitThisParam = isInstanceMethod(D); + unsigned NumArgs = getFunctionOrMethodNumParams(D) + HasImplicitThisParam; + + // Require an index that identifies the callback callee. + if (AL.getNumArgs() == 0) { + S.Diag(AL.getLoc(), diag::err_callback_attribute_no_callee) + << D->getSourceRange(); + return; + } + + // Helper to extract a number and validate its range. + auto ExtractArgIdx = [&](unsigned Pos, unsigned Min) -> int { + Expr *IdxExpr = AL.getArgAsExpr(Pos); + uint32_t Idx; + if (!checkUInt32Argument(S, AL, IdxExpr, Idx, Pos + 1, true)) + return -1; + + if (Idx < Min || Idx > NumArgs) { + S.Diag(AL.getLoc(), diag::err_attribute_argument_out_of_bounds) + << AL << (Pos + 1) << IdxExpr->getSourceRange(); + return -1; + } + // Adjust for an implicit "this" argument. + return Idx + HasImplicitThisParam; + }; + + // If the callee index is 0 it is invalid (we start counting with 1). + int CalleeIdx = ExtractArgIdx(0, 1); + if (CalleeIdx == -1) + return; + + SmallVector PayloadIndices; + for (unsigned i = 1, e = AL.getNumArgs(); i < e; i++) { + // A payload index can be 0 to indicate an unknown value. + int PayloadIdx = ExtractArgIdx(i, 0); + if (PayloadIdx == -1) + return; + PayloadIndices.push_back(PayloadIdx); + } + + // TODO: Check the type of the callee argument and if the number of unknown + // and forwarded arguments matches the type. + + // Do not allow multiple callback attributes. + if (D->hasAttr()) { + S.Diag(AL.getLoc(), diag::err_callback_attribute_multiple) << AL.getRange(); + return; + } + + D->addAttr(::new (S.Context) CallbackAttr( + AL.getRange(), S.Context, CalleeIdx, PayloadIndices.data(), + PayloadIndices.size(), AL.getAttributeSpellingListIndex())); +} + static void handleTransparentUnionAttr(Sema &S, Decl *D, const ParsedAttr &AL) { // Try to find the underlying union declaration. RecordDecl *RD = nullptr; @@ -6272,6 +6330,9 @@ case ParsedAttr::AT_FormatArg: handleFormatArgAttr(S, D, AL); break; + case ParsedAttr::AT_Callback: + handleCallbackAttr(S, D, AL); + break; case ParsedAttr::AT_CUDAGlobal: handleGlobalAttr(S, D, AL); break; Index: test/CodeGen/callback_annotated.c =================================================================== --- /dev/null +++ test/CodeGen/callback_annotated.c @@ -0,0 +1,70 @@ +// RUN: %clang_cc1 -triple i386-unknown-unknown -fopenmp -O1 %s -emit-llvm -o - | FileCheck %s --check-prefix=RUN1 +// RUN: %clang_cc1 -triple i386-unknown-unknown -fopenmp -O1 %s -emit-llvm -o - | FileCheck %s --check-prefix=RUN2 +// RUN: %clang_cc1 -triple i386-unknown-unknown -fopenmp -O1 %s -emit-llvm -o - | opt -ipconstprop -S | FileCheck --check-prefix=IPCP %s + +// RUN1-DAG: @broker0({{[^#]*#[0-9]+}} !callback ![[cid0:[0-9]+]] +__attribute__((callback (1, 2))) +void* broker0(void* (*callee)(void *), void *payload) { + return callee(payload); +} + +// RUN1-DAG: @broker1({{[^#]*#[0-9]+}} !callback ![[cid1:[0-9]+]] +__attribute__((callback (2, 1))) +void* broker1(void *payload, void* (*callee)(void *)) { + return broker0(callee, payload); +} + +// RUN1-DAG: declare !callback ![[cid2:[0-9]+]] i8* @broker2 +__attribute__((callback (1))) +void* broker2(void (*callee)(void)); + +// RUN1-DAG: declare !callback ![[cid3:[0-9]+]] i8* @broker3 +__attribute__((callback (4, 1, 2, 3))) +void* broker3(int, int, int, int (*callee)(int, int, int), int); + +// RUN1-DAG: declare !callback ![[cid4:[0-9]+]] i8* @broker4 +__attribute__((callback (4, 0, 1, 0))) +void* broker4(int, int, int, int (*callee)(int, int, int), int); + +// RUN1-DAG: declare !callback ![[cid5:[0-9]+]] i8* @broker5 +__attribute__((callback (4, 5, 5, 2))) +void* broker5(int, int, int, int (*callee)(int, int, int), int); + + +static void *VoidPtr2VoidPtr(void *payload) { +// RUN2: ret i8* %payload +// IPCP: ret i8* null + return payload; +} + +static int ThreeInt2Int(int a, int b, int c) { +// RUN2: define internal i32 @ThreeInt2Int(i32 %a, i32 %b, i32 %c) +// RUN2-NEXT: entry: +// RUN2-NEXT: %mul = mul nsw i32 %b, %a +// RUN2-NEXT: %add = add nsw i32 %mul, %c +// RUN2-NEXT: ret i32 %add + +// IPCP: define internal i32 @ThreeInt2Int(i32 %a, i32 %b, i32 %c) +// IPCP-NEXT: entry: +// IPCP-NEXT: %mul = mul nsw i32 4, %a +// IPCP-NEXT: %add = add nsw i32 %mul, %c +// IPCP-NEXT: ret i32 %add + + return a * b + c; +} + +void foo() { + broker0(VoidPtr2VoidPtr, 0l); + broker1(0l, VoidPtr2VoidPtr); + broker2(foo); + broker3(1, 4, 5, ThreeInt2Int, 1); + broker4(4, 2, 7, ThreeInt2Int, 0); + broker5(8, 0, 3, ThreeInt2Int, 4); +} + +// RUN1-DAG: ![[cid0]] = !{i1 false, i64 1, i64 2} +// RUN1-DAG: ![[cid1]] = !{i1 false, i64 2, i64 1} +// RUN1-DAG: ![[cid2]] = !{i1 false, i64 1} +// RUN1-DAG: ![[cid3]] = !{i1 false, i64 4, i64 1, i64 2, i64 3} +// RUN1-DAG: ![[cid4]] = !{i1 false, i64 4, i64 0, i64 1, i64 0} +// RUN1-DAG: ![[cid5]] = !{i1 false, i64 4, i64 5, i64 5, i64 2} Index: test/CodeGen/callback_openmp.c =================================================================== --- /dev/null +++ test/CodeGen/callback_openmp.c @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -triple i386-unknown-unknown -fopenmp -O1 %s -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -triple i386-unknown-unknown -fopenmp -O1 %s -emit-llvm -o - | opt -ipconstprop -S | FileCheck --check-prefix=IPCP %s + +// CHECK: declare !callback ![[cid:[0-9]+]] void @__kmpc_fork_call +// CHECK: declare !callback ![[cid]] void @__kmpc_fork_teams +// CHECK: ![[cid]] = !{i1 true, i64 3, i64 0, i64 0} + +void work1(int, int); +void work2(int, int); +void work12(int, int); + +void foo(int q) { + int p = 2; + + #pragma omp parallel firstprivate(q, p) + work1(p, q); +// IPCP: call void @work1(i32 2, i32 %{{[._a-zA-Z0-9]*}}) + + #pragma omp parallel for firstprivate(p, q) + for (int i = 0; i < q; i++) + work2(i, p); +// IPCP: call void @work2(i32 %{{[._a-zA-Z0-9]*}}, i32 2) + + #pragma omp target teams firstprivate(p) + work12(p, p); +// IPCP: call void @work12(i32 2, i32 2) +} Index: test/CodeGen/callback_pthread_create.c =================================================================== --- /dev/null +++ test/CodeGen/callback_pthread_create.c @@ -0,0 +1,31 @@ +// RUN: %clang -O1 %s -S -c -emit-llvm -o - | FileCheck %s +// RUN: %clang -O1 %s -S -c -emit-llvm -o - | opt -ipconstprop -S | FileCheck --check-prefix=IPCP %s + +// CHECK: declare !callback ![[cid:[0-9]+]] dso_local i32 @pthread_create +// CHECK: ![[cid]] = !{i1 false, i64 3, i64 4} + +#include + +const int GlobalVar = 0; + +static void *callee0(void *payload) { +// IPCP: define internal i8* @callee0 +// IPCP-NEXT: entry: +// IPCP-NEXT: ret i8* null + return payload; +} + +static void *callee1(void *payload) { +// IPCP: define internal i8* @callee1 +// IPCP-NEXT: entry: +// IPCP-NEXT: ret i8* bitcast (i32* @GlobalVar to i8*) + return payload; +} + +void foo() { + pthread_t MyFirstThread; + pthread_create(&MyFirstThread, NULL, callee0, NULL); + + pthread_t MySecondThread; + pthread_create(&MySecondThread, NULL, callee1, (void *)&GlobalVar); +}