diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h --- a/clang/include/clang/AST/DeclObjC.h +++ b/clang/include/clang/AST/DeclObjC.h @@ -487,6 +487,9 @@ /// True if the method is tagged as objc_direct bool isDirectMethod() const; + /// True if the method is tagged as objc_direct and objc_direct_visible + bool isDirectMethodVisible() const; + /// True if the method has a parameter that's destroyed in the callee. bool hasParamDestroyedInCallee() const; diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2248,6 +2248,13 @@ let Documentation = [ObjCDirectMembersDocs]; } +def ObjCDirectVisible : Attr { + let Spellings = [Clang<"objc_direct_visible">]; + let Subjects = SubjectList<[ObjCMethod], ErrorDiag>; + let LangOpts = [ObjC]; + let Documentation = [ObjCDirectVisibleDocs]; +} + def ObjCNonRuntimeProtocol : Attr { let Spellings = [Clang<"objc_non_runtime_protocol">]; let Subjects = SubjectList<[ObjCProtocol], ErrorDiag>; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5377,6 +5377,15 @@ }]; } +def ObjCDirectVisibleDocs : Documentation { + let Category = DocCatDecl; + let Content = [{ +This attribute specifies that the ``objc_direct`` method it is placed on is to +be vislble (ie skip marking for hidden linkage) so that it can be called across +link unit boundaries. + }]; +} + def ObjCNonRuntimeProtocolDocs : Documentation { let Category = DocCatDecl; let Content = [{ diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp --- a/clang/lib/AST/DeclObjC.cpp +++ b/clang/lib/AST/DeclObjC.cpp @@ -838,6 +838,10 @@ !getASTContext().getLangOpts().ObjCDisableDirectMethodsForTesting; } +bool ObjCMethodDecl::isDirectMethodVisible() const { + return isDirectMethod() && hasAttr(); +} + bool ObjCMethodDecl::isThisDeclarationADesignatedInitializer() const { return getMethodFamily() == OMF_init && hasAttr(); diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp --- a/clang/lib/CodeGen/CGObjC.cpp +++ b/clang/lib/CodeGen/CGObjC.cpp @@ -761,6 +761,8 @@ const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD); if (OMD->isDirectMethod()) { Fn->setVisibility(llvm::Function::HiddenVisibility); + if (OMD->isDirectMethodVisible()) + Fn->setVisibility(llvm::Function::DefaultVisibility); CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/false); CGM.SetLLVMFunctionAttributesForDefinition(OMD, Fn); } else { diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -4021,6 +4021,20 @@ DirectMethodDefinitions.insert(std::make_pair(COMD, Fn)); } + // If we want to explicitly export objc_direct methods, we need a name fixup. + if (OMD->isDirectMethodVisible() && Fn->getName().str()[0] == '\1') { + // Drop '\1' to work with dlsym. + std::string Name = Fn->getName().str().substr(1); + + assert(Name[0] == '-' || Name[0] == '+'); + assert(Name[1] == '[' && Name[Name.length() - 1] == ']'); + + // replace "[ ]" by "< >" to avoid strip by ld64. + Name = Name.substr(0, 1) + "<" + Name.substr(2, Name.length() - 3) + ">"; + + Fn->setName(Name); + } + return Fn; } diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2943,6 +2943,20 @@ } } +static void handleObjCDirectVisibleAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + // objc_direct cannot be set on methods declared in the context of a protocol + if (isa(D->getDeclContext())) { + S.Diag(AL.getLoc(), diag::err_objc_direct_on_protocol) << false; + return; + } + + if (S.getLangOpts().ObjCRuntime.allowsDirectDispatch()) { + handleSimpleAttribute(S, D, AL); + } else { + S.Diag(AL.getLoc(), diag::warn_objc_direct_ignored) << AL; + } +} + static void handleObjCMethodFamilyAttr(Sema &S, Decl *D, const ParsedAttr &AL) { const auto *M = cast(D); if (!AL.isArgIdent(0)) { @@ -8782,6 +8796,9 @@ case ParsedAttr::AT_ObjCDirect: handleObjCDirectAttr(S, D, AL); break; + case ParsedAttr::AT_ObjCDirectVisible: + handleObjCDirectVisibleAttr(S, D, AL); + break; case ParsedAttr::AT_ObjCDirectMembers: handleObjCDirectMembersAttr(S, D, AL); handleSimpleAttribute(S, D, AL); diff --git a/clang/test/CodeGenObjC/objc-direct-wrapper.m b/clang/test/CodeGenObjC/objc-direct-wrapper.m new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenObjC/objc-direct-wrapper.m @@ -0,0 +1,41 @@ +// RUN: %clang -fobjc-arc -Wno-objc-root-class -ObjC -fobjc-runtime=ios -FFoundation \ +// RUN: -target x86_64-apple-macosx10.15.0 -c -o - %s | \ +// RUN: llvm-nm - | FileCheck -check-prefix=CHECK-WRAPPER %s + +// RUN: %clang -fobjc-arc -Wno-objc-root-class \ +// RUN: -target x86_64-apple-macosx10.15.0 \ +// RUN: -ObjC -fobjc-runtime=ios -FFoundation -c -o - %s | llvm-nm - | \ +// RUN: FileCheck -check-prefix=CHECK-DEFAULT %s + +// RUN: %clang -fobjc-arc -Wno-objc-root-class \ +// RUN: -target x86_64-apple-macosx10.15.0 \ +// RUN: -ObjC -fobjc-runtime=ios -FFoundation -c -o - %s -S -emit-llvm | \ +// RUN: FileCheck -check-prefix=CHECK-WRAPPER-IR-DEFINE %s + +// RUN: %clang -fobjc-arc -Wno-objc-root-class -DNO_OBJC_IMPL \ +// RUN: -target x86_64-apple-macosx10.15.0 \ +// RUN: -ObjC -fobjc-runtime=ios -FFoundation -c -o - %s -S -emit-llvm | \ +// RUN: FileCheck -check-prefix=CHECK-WRAPPER-IR-DECLARE %s + +// CHECK-WRAPPER: T _- + // TODO: Fix this +// CHECK-DEFAULT: T -[C testMethod:bar:] +// CHECK-WRAPPER-IR-DEFINE: define {{(dso_local )?}}void @"-" +// CHECK-WRAPPER-IR-DECLARE: declare {{(dso_local )?}}void @"-" + +@interface C +- (void)testMethod:(int)arg1 bar:(float)arg2 __attribute((objc_direct)) __attribute__((objc_direct_visible)); +@end + +#ifndef NO_OBJC_IMPL +@implementation C +- (void)testMethod:(int)arg1 bar:(float)arg2 __attribute((objc_direct)) __attribute__((objc_direct_visible)) { +} +@end +#endif + +C *c; + +void f() { + [c testMethod:1 bar:1.0]; +}