diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h index 4663603f79754..69c36b77da6c2 100644 --- a/clang/include/clang/AST/DeclObjC.h +++ b/clang/include/clang/AST/DeclObjC.h @@ -482,6 +482,12 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext { /// True if the method is tagged as objc_direct bool isDirectMethod() const; + // Only direct instance method that have a fixed number of arguments can have + // nil check thunk functions. + bool canHaveNilCheckThunk() const { + return isDirectMethod() && isInstanceMethod() && !isVariadic(); + } + /// True if the method has a parameter that's destroyed in the callee. bool hasParamDestroyedInCallee() const; diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h index a0162fb7125fe..6b5c15d2ccf18 100644 --- a/clang/include/clang/AST/Mangle.h +++ b/clang/include/clang/AST/Mangle.h @@ -40,6 +40,13 @@ struct ThisAdjustment; struct ThunkInfo; class VarDecl; +/// Extract mangling function name from MangleContext such that swift can call +/// it to prepare for ObjCDirect in swift. +void mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte, + bool isInstanceMethod, StringRef ClassName, + std::optional CategoryName, + StringRef MethodName, bool isNilCheckThunk); + /// MangleContext - Context for tracking state which persists across multiple /// calls to the C++ name mangler. class MangleContext { @@ -153,7 +160,8 @@ class MangleContext { void mangleObjCMethodName(const ObjCMethodDecl *MD, raw_ostream &OS, bool includePrefixByte = true, - bool includeCategoryNamespace = true) const; + bool includeCategoryNamespace = true, + bool hasNilCheck = true) const; void mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD, raw_ostream &) const; diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index c5990fb248689..9ec1d90e1d960 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -361,6 +361,7 @@ CODEGENOPT(EmitLLVMUseLists, 1, 0) ///< Control whether to serialize use-lists. CODEGENOPT(WholeProgramVTables, 1, 0) ///< Whether to apply whole-program /// vtable optimization. +CODEGENOPT(ObjCEmitNilCheckThunk , 1, 0) ///< Whether objc_direct methods should emit a nil check thunk. CODEGENOPT(VirtualFunctionElimination, 1, 0) ///< Whether to apply the dead /// virtual function elimination diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 85ca523c44157..a529ef47ac9fc 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -371,6 +371,7 @@ LANGOPT(IncludeDefaultHeader, 1, 0, "Include default header file for OpenCL") LANGOPT(DeclareOpenCLBuiltins, 1, 0, "Declare OpenCL builtin functions") BENIGN_LANGOPT(DelayedTemplateParsing , 1, 0, "delayed template parsing") LANGOPT(BlocksRuntimeOptional , 1, 0, "optional blocks runtime") +LANGOPT(ObjCEmitNilCheckThunk, 1, 0, "Emit a thunk to do nil check for objc direct methods") LANGOPT( CompleteMemberPointers, 1, 0, "Require member pointer base types to be complete at the point where the " diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index c0f469e04375c..fd77449f7263d 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3049,6 +3049,8 @@ def fthin_link_bitcode_EQ : Joined<["-"], "fthin-link-bitcode=">, Visibility<[ClangOption, CLOption, CC1Option]>, Group, HelpText<"Write minimized bitcode to for the ThinLTO thin link only">, MarshallingInfoString>; +def fobjc_emit_nil_check_thunk: + Flag<["-"], "fobjc-emit-nil-check-thunk">, Visibility<[ClangOption, CC1Option]>, Group; defm fat_lto_objects : BoolFOption<"fat-lto-objects", CodeGenOpts<"FatLTO">, DefaultFalse, PosFlag, diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp index 741c031a40385..e89faa85166b6 100644 --- a/clang/lib/AST/Mangle.cpp +++ b/clang/lib/AST/Mangle.cpp @@ -29,6 +29,24 @@ using namespace clang; +void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte, + bool isInstanceMethod, StringRef ClassName, + std::optional CategoryName, + StringRef MethodName, bool hasNilCheck) { + // \01+[ContainerName(CategoryName) SelectorName] + if (includePrefixByte) + OS << "\01"; + OS << (isInstanceMethod ? '-' : '+'); + OS << '['; + OS << ClassName; + if (CategoryName) + OS << "(" << *CategoryName << ")"; + OS << " "; + OS << MethodName; + OS << ']'; + if (!hasNilCheck) + OS << "_nonnull"; +} // FIXME: For blocks we currently mimic GCC's mangling scheme, which leaves // much to be desired. Come up with a better mangling scheme. @@ -328,7 +346,8 @@ void MangleContext::mangleBlock(const DeclContext *DC, const BlockDecl *BD, void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD, raw_ostream &OS, bool includePrefixByte, - bool includeCategoryNamespace) const { + bool includeCategoryNamespace, + bool hasNilCheck) const { if (getASTContext().getLangOpts().ObjCRuntime.isGNUFamily()) { // This is the mangling we've always used on the GNU runtimes, but it // has obvious collisions in the face of underscores within class @@ -362,26 +381,26 @@ void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD, } // \01+[ContainerName(CategoryName) SelectorName] - if (includePrefixByte) { - OS << '\01'; - } - OS << (MD->isInstanceMethod() ? '-' : '+') << '['; + auto CategoryName = std::optional(); + StringRef ClassName = ""; if (const auto *CID = MD->getCategory()) { if (const auto *CI = CID->getClassInterface()) { - OS << CI->getName(); + ClassName = CI->getName(); if (includeCategoryNamespace) { - OS << '(' << *CID << ')'; + CategoryName = CID->getName(); } } } else if (const auto *CD = dyn_cast(MD->getDeclContext())) { - OS << CD->getName(); + ClassName = CD->getName(); } else { llvm_unreachable("Unexpected ObjC method decl context"); } - OS << ' '; - MD->getSelector().print(OS); - OS << ']'; + std::string MethodName; + llvm::raw_string_ostream MethodNameOS(MethodName); + MD->getSelector().print(MethodNameOS); + clang::mangleObjCMethodName(OS, includePrefixByte, MD->isInstanceMethod(), + ClassName, CategoryName, MethodName, hasNilCheck); } void MangleContext::mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD, diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index c7fbbbc6fd40d..dddbdfcaa322c 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -2771,6 +2771,17 @@ void CodeGenModule::ConstructAttributeList(StringRef Name, ArgAttrs[IRArgs.first] = llvm::AttributeSet::get(getLLVMContext(), Attrs); } + // Direct method prologue should not contain nil check anymore. + // As a result, we can set `self` to be NonNull to prepare for further + // optimizations. + if (TargetDecl) { + auto OMD = dyn_cast(TargetDecl); + if (shouldHaveNilCheckThunk(OMD) && !IsThunk) { + auto IRArgs = IRFunctionArgs.getIRArgs(0); + ArgAttrs[IRArgs.first] = ArgAttrs[IRArgs.first].addAttribute( + getLLVMContext(), llvm::Attribute::NonNull); + } + } unsigned ArgNo = 0; for (CGFunctionInfo::const_arg_iterator I = FI.arg_begin(), @@ -3466,7 +3477,13 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, } } - if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) { + if (InnerFn) { + // Don't emit any arg except for `self` if we are in a thunk function. + // We still need self for nil check, other arguments aren't used in this + // function and thus are not needed. Avoid emitting them also prevents + // accidental release/retain. + EmitParmDecl(*Args[0], ArgVals[0], 1); + } else if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) { for (int I = Args.size() - 1; I >= 0; --I) EmitParmDecl(*Args[I], ArgVals[I], I + 1); } else { diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp index 27c7c2fa9cba1..1fd4c1d2c8a0f 100644 --- a/clang/lib/CodeGen/CGObjC.cpp +++ b/clang/lib/CodeGen/CGObjC.cpp @@ -756,12 +756,13 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD, if (OMD->hasAttr()) DebugInfo = nullptr; // disable debug info indefinitely for this function - llvm::Function *Fn = CGM.getObjCRuntime().GenerateMethod(OMD, CD); + bool isInner = CGM.shouldHaveNilCheckThunk(OMD) && !InnerFn; + llvm::Function *Fn = CGM.getObjCRuntime().GenerateMethod(OMD, CD, !isInner); const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD); if (OMD->isDirectMethod()) { Fn->setVisibility(llvm::Function::HiddenVisibility); - CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/false); + CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/InnerFn); CGM.SetLLVMFunctionAttributesForDefinition(OMD, Fn); } else { CGM.SetInternalFunctionAttributes(OMD, Fn, FI); @@ -780,10 +781,14 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD, OMD->getLocation(), StartLoc); if (OMD->isDirectMethod()) { - // This function is a direct call, it has to implement a nil check - // on entry. - // - // TODO: possibly have several entry points to elide the check + // Having `InnerFn` indicates that we are generating a nil check thunk. + // In that case our job is done here. + if (InnerFn) + return; + // Only NeXTFamily can have nil check thunk. + if (CGM.shouldHaveNilCheckThunk(OMD)) + // Go generate a nil check thunk around `Fn` + CodeGenFunction(CGM, /*InnerFn=*/Fn).GenerateObjCDirectThunk(OMD, CD); CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD); } @@ -1637,6 +1642,44 @@ void CodeGenFunction::GenerateObjCSetter(ObjCImplementationDecl *IMP, FinishFunction(OMD->getEndLoc()); } +void CodeGenFunction::GenerateObjCDirectThunk(const ObjCMethodDecl *OMD, + const ObjCContainerDecl *CD) { + assert(InnerFn && CGM.shouldHaveNilCheckThunk(OMD) && + "Should only generate wrapper when the flag is set."); + StartObjCMethod(OMD, CD); + + // Manually pop all the clean up that doesn't need to happen in the outer + // function. InnerFn will do this for us. + while (EHStack.stable_begin() != PrologueCleanupDepth) + EHStack.popCleanup(); + + // Generate a nil check. + CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, CurFn, OMD, CD); + // Call the InnerFn and pass the return value + SmallVector Args(CurFn->arg_size()); + std::transform(CurFn->arg_begin(), CurFn->arg_end(), Args.begin(), + [](llvm::Argument &arg) { return &arg; }); + + // This will be optimized into a tail call. + auto *CallInst = EmitCallOrInvoke(InnerFn, Args); + // Preserve the inner function's attributes to the call instruction. + CallInst->setAttributes(InnerFn->getAttributes()); + llvm::Value *RetVal = CallInst; + + // If `AutoreleaseResult` is set, the return value is not void. + if (AutoreleaseResult) + RetVal = EmitARCRetainAutoreleasedReturnValue(RetVal); + + // This excessive store is totally unnecessary. + // But `FinishFunction` really wants us to store the result so it can + // clean up the function properly. + // The unnecessary store-load of the ret value will be optimized out anyway. + if (!CurFn->getReturnType()->isVoidTy()) + Builder.CreateStore(RetVal, ReturnValue); + + // Nil check's end location is the function's start location. + FinishFunction(OMD->getBeginLoc()); +} namespace { struct DestroyIvar final : EHScopeStack::Cleanup { private: diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index 33f6b0470061f..3b81e0c2d58d6 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -595,7 +595,13 @@ class CGObjCGNU : public CGObjCRuntime { llvm::Constant *GetEHType(QualType T) override; llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD, - const ObjCContainerDecl *CD) override; + const ObjCContainerDecl *CD, + bool isThunk) override { + // isThunk is irrelevent for GNU. + return GenerateMethod(OMD, CD); + }; + llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD, + const ObjCContainerDecl *CD); // Map to unify direct method definitions. llvm::DenseMap diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp index 1c23a8b4db918..e2571cbf03e04 100644 --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -1049,12 +1049,13 @@ class CGObjCCommonMac : public CodeGen::CGObjCRuntime { ConstantAddress GenerateConstantString(const StringLiteral *SL) override; ConstantAddress GenerateConstantNSString(const StringLiteral *SL); - llvm::Function * - GenerateMethod(const ObjCMethodDecl *OMD, - const ObjCContainerDecl *CD = nullptr) override; + llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD, + const ObjCContainerDecl *CD = nullptr, + bool isThunk = true) override; llvm::Function *GenerateDirectMethod(const ObjCMethodDecl *OMD, - const ObjCContainerDecl *CD); + const ObjCContainerDecl *CD, + bool isThunk); void GenerateDirectMethodPrologue(CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, @@ -2078,7 +2079,8 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend( llvm::FunctionCallee Fn = nullptr; if (Method && Method->isDirectMethod()) { assert(!IsSuper); - Fn = GenerateDirectMethod(Method, Method->getClassInterface()); + Fn = GenerateDirectMethod(Method, Method->getClassInterface(), + /*isThunk=*/true); // Direct methods will synthesize the proper `_cmd` internally, // so just don't bother with setting the `_cmd` argument. RequiresSelValue = false; @@ -2102,10 +2104,6 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend( : ObjCTypes.getSendFn(IsSuper); } - // Cast function to proper signature - llvm::Constant *BitcastFn = cast( - CGF.Builder.CreateBitCast(Fn.getCallee(), MSI.MessengerType)); - // We don't need to emit a null check to zero out an indirect result if the // result is ignored. if (Return.isUnused()) @@ -2115,6 +2113,16 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend( if (!RequiresNullCheck && Method && Method->hasParamDestroyedInCallee()) RequiresNullCheck = true; + // `RequiresNullCheck` will do the null check at the caller site. + // In that case, a direct instance method can skip the null check and call the + // inner function instead. + if (CGM.shouldHaveNilCheckThunk(Method)) + if (RequiresNullCheck || !ReceiverCanBeNull) + Fn = GenerateDirectMethod(Method, Method->getClassInterface(), + /*isThunk=*/false); + // Cast function to proper signature + llvm::Constant *BitcastFn = cast( + CGF.Builder.CreateBitCast(Fn.getCallee(), MSI.MessengerType)); NullReturnState nullReturn; if (RequiresNullCheck) { nullReturn.init(CGF, Arg0); @@ -3841,11 +3849,12 @@ CGObjCMac::emitMethodList(Twine name, MethodListType MLT, } llvm::Function *CGObjCCommonMac::GenerateMethod(const ObjCMethodDecl *OMD, - const ObjCContainerDecl *CD) { + const ObjCContainerDecl *CD, + bool isThunk) { llvm::Function *Method; if (OMD->isDirectMethod()) { - Method = GenerateDirectMethod(OMD, CD); + Method = GenerateDirectMethod(OMD, CD, isThunk); } else { auto Name = getSymbolNameForMethod(OMD); @@ -3861,14 +3870,13 @@ llvm::Function *CGObjCCommonMac::GenerateMethod(const ObjCMethodDecl *OMD, return Method; } -llvm::Function * -CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD, - const ObjCContainerDecl *CD) { +llvm::Function *CGObjCCommonMac::GenerateDirectMethod( + const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD, bool isThunk) { auto *COMD = OMD->getCanonicalDecl(); auto I = DirectMethodDefinitions.find(COMD); llvm::Function *OldFn = nullptr, *Fn = nullptr; - if (I != DirectMethodDefinitions.end()) { + if (isThunk && I != DirectMethodDefinitions.end()) { // Objective-C allows for the declaration and implementation types // to differ slightly. // @@ -3897,11 +3905,16 @@ CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD, // Replace the cached function in the map. I->second = Fn; } else { - auto Name = getSymbolNameForMethod(OMD, /*include category*/ false); - - Fn = llvm::Function::Create(MethodTy, llvm::GlobalValue::ExternalLinkage, - Name, &CGM.getModule()); - DirectMethodDefinitions.insert(std::make_pair(COMD, Fn)); + auto Name = + getSymbolNameForMethod(OMD, /*include category*/ false, isThunk); + // Non-thunk functions are not cached and may be repeatedly created. + // Therefore, we try to find it before we create one. + Fn = CGM.getModule().getFunction(Name); + if (!Fn) + Fn = llvm::Function::Create(MethodTy, llvm::GlobalValue::ExternalLinkage, + Name, &CGM.getModule()); + if (isThunk) + DirectMethodDefinitions.insert(std::make_pair(COMD, Fn)); } return Fn; @@ -3914,6 +3927,8 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue( bool ReceiverCanBeNull = true; auto selfAddr = CGF.GetAddrOfLocalVar(OMD->getSelfDecl()); auto selfValue = Builder.CreateLoad(selfAddr); + bool shouldHaveNilCheckThunk = CGF.CGM.shouldHaveNilCheckThunk(OMD); + bool isNilCheckThunk = shouldHaveNilCheckThunk && CGF.InnerFn; // Generate: // @@ -3955,7 +3970,9 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue( ReceiverCanBeNull = isWeakLinkedClass(OID); } - if (ReceiverCanBeNull) { + // Only emit nil check if this is a nil check thunk or the method + // decides that its receiver can be null + if (isNilCheckThunk || (!shouldHaveNilCheckThunk && ReceiverCanBeNull)) { llvm::BasicBlock *SelfIsNilBlock = CGF.createBasicBlock("objc_direct_method.self_is_nil"); llvm::BasicBlock *ContBlock = @@ -3985,14 +4002,19 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue( Builder.SetInsertPoint(ContBlock); } - // only synthesize _cmd if it's referenced - if (OMD->getCmdDecl()->isUsed()) { + // Only synthesize _cmd if it's referenced + // However, a nil check thunk doesn't need _cmd even if it's referenced + if (!isNilCheckThunk && OMD->getCmdDecl()->isUsed()) { // `_cmd` is not a parameter to direct methods, so storage must be // explicitly declared for it. CGF.EmitVarDecl(*OMD->getCmdDecl()); Builder.CreateStore(GetSelector(CGF, OMD), CGF.GetAddrOfLocalVar(OMD->getCmdDecl())); } + + // It's possible that selfValue is never used. + if (selfValue->use_empty()) + selfValue->eraseFromParent(); } llvm::GlobalVariable * diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp index dfb0fd14d93ac..a0cc65e7eb211 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.cpp +++ b/clang/lib/CodeGen/CGObjCRuntime.cpp @@ -468,11 +468,12 @@ clang::CodeGen::emitObjCProtocolObject(CodeGenModule &CGM, } std::string CGObjCRuntime::getSymbolNameForMethod(const ObjCMethodDecl *OMD, - bool includeCategoryName) { + bool includeCategoryName, + bool isThunk) { std::string buffer; llvm::raw_string_ostream out(buffer); - CGM.getCXXABI().getMangleContext().mangleObjCMethodName(OMD, out, - /*includePrefixByte=*/true, - includeCategoryName); + CGM.getCXXABI().getMangleContext().mangleObjCMethodName( + OMD, out, + /*includePrefixByte=*/true, includeCategoryName, isThunk); return buffer; } diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h index 72997bf6348ae..26217483126b4 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.h +++ b/clang/lib/CodeGen/CGObjCRuntime.h @@ -117,7 +117,8 @@ class CGObjCRuntime { virtual ~CGObjCRuntime(); std::string getSymbolNameForMethod(const ObjCMethodDecl *method, - bool includeCategoryName = true); + bool includeCategoryName = true, + bool isThunk = true); /// Generate the function required to register all Objective-C components in /// this compilation unit with the runtime library. @@ -223,7 +224,8 @@ class CGObjCRuntime { // should also be generating the loads of the parameters, as the runtime // should have full control over how parameters are passed. virtual llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD, - const ObjCContainerDecl *CD) = 0; + const ObjCContainerDecl *CD, + bool isThunk = true) = 0; /// Generates prologue for direct Objective-C Methods. virtual void GenerateDirectMethodPrologue(CodeGenFunction &CGF, diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 4d29ceace646f..bf3bb527cdfad 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -74,6 +74,13 @@ static bool shouldEmitLifetimeMarkers(const CodeGenOptions &CGOpts, return CGOpts.OptimizationLevel != 0; } +CodeGenFunction::CodeGenFunction(CodeGenModule &cgm, llvm::Function *Inner, + bool suppressNewContext) + : CodeGenFunction(cgm, suppressNewContext) { + InnerFn = Inner; + assert(InnerFn && "The inner function provided should not be null"); +} + CodeGenFunction::CodeGenFunction(CodeGenModule &cgm, bool suppressNewContext) : CodeGenTypeCache(cgm), CGM(cgm), Target(cgm.getTarget()), Builder(cgm, cgm.getModule().getContext(), llvm::ConstantFolder(), diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 4c5e8a8a44926..6d410609fbb72 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -1673,6 +1673,10 @@ class CodeGenFunction : public CodeGenTypeCache { } void markStmtMaybeUsed(const Stmt *S) { PGO.markStmtMaybeUsed(S); } + /// If `InnerFn` is set, this CGF generates a thunk function that does the nil + /// check before calling `InnerFn`. `InnerFn` has to be an objc_direct method. + llvm::Function *InnerFn = nullptr; + /// Increment the profiler's counter for the given statement by \p StepV. /// If \p StepV is null, the default increment is 1. void incrementProfileCounter(const Stmt *S, llvm::Value *StepV = nullptr); @@ -2166,6 +2170,8 @@ class CodeGenFunction : public CodeGenTypeCache { public: CodeGenFunction(CodeGenModule &cgm, bool suppressNewContext = false); + CodeGenFunction(CodeGenModule &cgm, llvm::Function *inner, + bool suppressNewContext = false); ~CodeGenFunction(); CodeGenTypes &getTypes() const { return CGM.getTypes(); } @@ -2317,6 +2323,8 @@ class CodeGenFunction : public CodeGenTypeCache { void generateObjCSetterBody(const ObjCImplementationDecl *classImpl, const ObjCPropertyImplDecl *propImpl, llvm::Constant *AtomicHelperFn); + void GenerateObjCDirectThunk(const ObjCMethodDecl *OMD, + const ObjCContainerDecl *CD); //===--------------------------------------------------------------------===// // Block Bits diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 59f400570fb7a..84439d08b3366 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -327,6 +327,14 @@ class CodeGenModule : public CodeGenTypeCache { void operator=(const CodeGenModule &) = delete; public: + // Returns true if the nil check thunk flag is turned on and the method is + // thunkable. A method is thunkable if it is a direct instance method that + // have a fixed number of arguments. + bool shouldHaveNilCheckThunk(const ObjCMethodDecl *OMD) const { + return getCodeGenOpts().ObjCEmitNilCheckThunk && + getLangOpts().ObjCRuntime.isNeXTFamily() && OMD && + OMD->canHaveNilCheckThunk(); + } struct Structor { Structor() : Priority(0), LexOrder(~0u), Initializer(nullptr), diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 762f8af886920..f1c2235cbcbfb 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -8131,6 +8131,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Input.getInputArg().renderAsInput(Args, CmdArgs); } + if (Args.hasArg(options::OPT_fobjc_emit_nil_check_thunk)) + CmdArgs.push_back("-fobjc-emit-nil-check-thunk"); + if (D.CC1Main && !D.CCGenDiagnostics) { // Invoke the CC1 directly in this process C.addCommand(std::make_unique( diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 8ff62ae2552c3..ef0298471924b 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1609,6 +1609,9 @@ void CompilerInvocationBase::GenerateCodeGenArgs(const CodeGenOptions &Opts, else if (!Opts.DirectAccessExternalData && LangOpts->PICLevel == 0) GenerateArg(Consumer, OPT_fno_direct_access_external_data); + if (Opts.ObjCEmitNilCheckThunk) + GenerateArg(Consumer, OPT_fobjc_emit_nil_check_thunk); + std::optional DebugInfoVal; switch (Opts.DebugInfo) { case llvm::codegenoptions::DebugLineTablesOnly: @@ -1942,6 +1945,8 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, Opts.setDebugInfo(llvm::codegenoptions::LimitedDebugInfo); } + Opts.ObjCEmitNilCheckThunk = Args.hasArg(OPT_fobjc_emit_nil_check_thunk); + for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) { auto Split = StringRef(Arg).split('='); Opts.DebugPrefixMap.emplace_back(Split.first, Split.second); @@ -4200,6 +4205,8 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.ZOSExt = Args.hasFlag(OPT_fzos_extensions, OPT_fno_zos_extensions, T.isOSzOS()); + Opts.ObjCEmitNilCheckThunk = Args.hasArg(OPT_fobjc_emit_nil_check_thunk); + Opts.Blocks = Args.hasArg(OPT_fblocks) || (Opts.OpenCL && Opts.OpenCLVersion == 200); diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-linkedlist.m b/clang/test/CodeGenObjC/direct-method-nil-check-linkedlist.m new file mode 100644 index 0000000000000..6dcd9242e8a23 --- /dev/null +++ b/clang/test/CodeGenObjC/direct-method-nil-check-linkedlist.m @@ -0,0 +1,138 @@ +// REQUIRES: system-darwin + +// RUN: mkdir -p %t + +// RUN: %clang -fobjc-emit-nil-check-thunk \ +// RUN: -target arm64-apple-darwin -fobjc-arc \ +// RUN: -O2 -framework Foundation %s -o %t/thunk-linkedlist + +// RUN: %t/thunk-linkedlist 8 7 6 | FileCheck %s --check-prefix=CHECK-EXE +#import + +@interface LinkedList: NSObject +@property(direct, readonly, nonatomic) int v; +@property(direct, strong, nonatomic) LinkedList* next; +@property(direct, readonly, nonatomic) int instanceId; +@property(strong, nonatomic, direct) void ( ^ printBlock )( void ); +@property(class) int numInstances; + +// Prints instantceId before dealloc +- (void) dealloc; +- (instancetype)initWithV:(int)v Next:(id)next __attribute__((objc_direct)); +- (instancetype)clone __attribute__((objc_direct)); +- (void)print __attribute__((objc_direct)); +- (instancetype) reverseWithPrev:(id) prev __attribute__((objc_direct)); +- (void) printWithFormat:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2) __attribute__((objc_direct)); +- (int) size __attribute__((objc_direct)); +- (int) sum __attribute__((objc_direct)); +- (double) avg __attribute__((objc_direct)); +@end + +@implementation LinkedList +@dynamic numInstances; +static int numInstances=0; + +- (void) dealloc { + printf("Dealloc id: %d\n", self.instanceId); +} + +- (instancetype)initWithV:(int)v Next:(id)next{ + if (self = [super init]) { + _v = v; + _next = next; + _instanceId = numInstances; + LinkedList* __weak weakSelf = self; + _printBlock = ^void(void) { [weakSelf print]; }; + numInstances++; + printf("Alloc id: %d, v: %d\n", self.instanceId, self.v); + } + return self; +} +- (instancetype) clone { + return [[LinkedList alloc] initWithV:self.v Next:[self.next clone]]; +} + +- (void) print { + printf("id: %d, v: %d\n", self.instanceId, self.v); + [self.next print]; +} + +- (void) printWithFormat:(NSString*)format, ...{ + [self print]; + NSString *description; + if ([format length] > 0) { + va_list args; + va_start(args, format); + description = [[NSString alloc] initWithFormat:(id)format arguments:args]; + va_end(args); + } + printf("%s", description.UTF8String); +} + +- (LinkedList*) reverseWithPrev:(LinkedList*) prev{ + LinkedList* newHead = (self.next == nil) ? self : [self.next reverseWithPrev:self]; + self.next = prev; + return newHead; +} + +- (int) size { + return 1 + [self.next size]; +} +- (int) sum { + return self.v + [self.next sum]; +} +- (double) avg { + return (double)[self sum] / (double)[self size]; +} +@end + +int main(int argc, char** argv) { // argv = ["8", "7", "6"] +@autoreleasepool { + // CHECK-EXE: Alloc id: 0, v: 7 + // CHECK-EXE: Alloc id: 1, v: 8 + LinkedList* ll = [[LinkedList alloc] initWithV:atoi(argv[1]) Next:[[LinkedList alloc] initWithV:atoi(argv[2]) Next:nil]]; + // CHECK-EXE: Alloc id: 2, v: 6 + ll.next.next = [[LinkedList alloc] initWithV:atoi(argv[3]) Next:nil]; + // CHECK-EXE: id: 1, v: 8 + // CHECK-EXE: id: 0, v: 7 + // CHECK-EXE: id: 2, v: 6 + [ll print]; + + // Because of the recursive clone, the tail is allocated first. + // CHECK-EXE: Alloc id: 3, v: 6 + // CHECK-EXE: Alloc id: 4, v: 7 + // CHECK-EXE: Alloc id: 5, v: 8 + LinkedList* cloned = [ll clone]; + + // CHECK-EXE: id: 5, v: 8 + // CHECK-EXE: id: 4, v: 7 + // CHECK-EXE: id: 3, v: 6 + [cloned print]; + + // CHECK-EXE: id: 5, v: 8 + // CHECK-EXE: id: 4, v: 7 + // CHECK-EXE: id: 3, v: 6 + cloned.printBlock(); + + // CHECK-EXE: id: 5, v: 8 + // CHECK-EXE: id: 4, v: 7 + // CHECK-EXE: id: 3, v: 6 + // CHECK-EXE: Hello world, I'm cloned, I have 3 elements + [cloned printWithFormat:@"Hello world, I'm cloned, I have %d elements\n", [cloned size]]; + + ll = [ll reverseWithPrev:nil]; + // CHECK-EXE: id: 2, v: 6 + // CHECK-EXE: id: 0, v: 7 + // CHECK-EXE: id: 1, v: 8 + [ll print]; + + // All objects should be deallocated. + // CHECK-EXE: Dealloc + // CHECK-EXE: Dealloc + // CHECK-EXE: Dealloc + // CHECK-EXE: Dealloc + // CHECK-EXE: Dealloc + // CHECK-EXE: Dealloc +} + return 0; +} diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m new file mode 100644 index 0000000000000..57eada2250bdf --- /dev/null +++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m @@ -0,0 +1,153 @@ +// REQUIRES: system-darwin + +// RUN: mkdir -p %t + +// RUN: %clang -fobjc-emit-nil-check-thunk -S -emit-llvm \ +// RUN: -target arm64-apple-darwin -O0 %s -o - -fobjc-arc \ +// RUN: | FileCheck %s + +// RUN: %clang -fobjc-emit-nil-check-thunk -S -emit-llvm \ +// RUN: -target arm64-apple-darwin -O0 %s -o - \ +// RUN: | FileCheck --check-prefix=NO-ARC %s + +// RUN: %clang -fobjc-emit-nil-check-thunk \ +// RUN: -target arm64-apple-darwin -fobjc-arc \ +// RUN: -O2 -framework Foundation %s -o %t/shape + +// RUN: %t/shape 1 2 3 4 | FileCheck %s --check-prefix=CHECK-EXE + +// NO-ARC-NOT: autoreleaseReturnValue +// NO-ARC-NOT: retainAutoreleasedReturnValue +// NO-ARC-NOT: asm sideeffect "mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue" + +#import +#include "math.h" + +@interface Shape: NSObject +@property(direct, readonly) int x; +@property(direct, readonly) int y; +@property(direct) Shape* innerShape; +@property(class) int numInstances; +@property(direct) int instanceId; +- (void) dealloc; +- (instancetype)initWithX:(int)x Y:(int)y __attribute__((objc_direct, , visibility("default"))); +- (instancetype)initDefault __attribute__((objc_direct, , visibility("default"))); +- (double) distanceFrom: (Shape *) __attribute__((ns_consumed)) s __attribute__((objc_direct, , visibility("default"))); ++ (Shape *) default __attribute__((objc_direct, , visibility("default"))); +- (instancetype) clone __attribute__((objc_direct, , visibility("default"))); +@end + +@implementation Shape +@dynamic numInstances; +static int numInstances=0; + +- (void) dealloc { + printf("Dealloc %d\n", self.instanceId); +} +- (instancetype)initWithX:(int)x Y:(int)y { + if (self = [super init]) { + _x = x; + _y = y; + _innerShape = nil; + _instanceId = numInstances; + printf("Alloc %d\n", _instanceId); + numInstances++; + } + return self; +} + +// Thunk function should not release anything. +// CHECK-LABEL: define hidden ptr @"\01-[Shape initDefault]" +// CHECK-NOT: call void @llvm.objc.storeStrong +// CHECK-LABEL: } +- (instancetype)initDefault { + return [self initWithX:0 Y:0]; +} + +// CHECK-LABEL: define hidden ptr @"\01+[Shape default]" +// CHECK: [[SHAPE:%.*]] = call ptr @"\01-[Shape initDefault]" +// CHECK-NEXT: [[AUTORELEASE_SHAPE:%.*]] = tail call ptr @llvm.objc.autoreleaseReturnValue(ptr [[SHAPE]]) +// CHECK-NEXT: ret ptr [[AUTORELEASE_SHAPE]] +// CHECK-LABEL: } ++ (Shape*) default { + return [[Shape alloc] initDefault]; +} + +// CHECK-LABEL: define {{.*}} @"\01-[Shape clone]_nonnull" +// CHECK: [[CALL_INIT:%.*]] = call ptr @"\01-[Shape initWithX:Y:]" +// CHECK-NEXT: [[AUTORELEASE_CLONE:%.*]] = tail call ptr @llvm.objc.autoreleaseReturnValue(ptr [[CALL_INIT]]) +// CHECK-NEXT: ret ptr [[AUTORELEASE_CLONE]] +// CHECK-LABEL: } + +// CHECK-LABEL: define {{.*}} @"\01-[Shape clone]" +// CHECK: [[CALL_INNER:%.*]] = call ptr @"\01-[Shape clone]_nonnull" +// CHECK-NEXT: call void asm sideeffect "mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue", ""() +// CHECK-NEXT: [[RETAINED:%.*]] = call ptr @llvm.objc.retainAutoreleasedReturnValue(ptr [[CALL_INNER]]) +// CHECK-NEXT: store ptr [[RETAINED]], ptr [[RETADDR:%.*]] +// CHECK: [[RET:%.*]] = load ptr, ptr [[RETADDR]] +// CHECK-NEXT: [[AUTORELEASE_RET:%.*]] = tail call ptr @llvm.objc.autoreleaseReturnValue(ptr [[RET]]) +// CHECK-NEXT: ret ptr [[AUTORELEASE_RET]] +// CHECK-LABEL: } +- (instancetype) clone { + return [[Shape alloc] initWithX:self.x Y:self.y]; +} + +// InnerFn will release the value since it is "consumed". +// CHECK: define hidden double @"\01-[Shape distanceFrom:]_nonnull"(ptr noundef nonnull %{{.*}}, ptr noundef [[S:%.*]]) #0 { +// CHECK: {{%.*}} = alloca ptr +// CHECK: [[S_ADDR:%.*]] = alloca ptr +// CHECK: store ptr [[S]], ptr [[S_ADDR]] +// CHECK: call void @llvm.objc.storeStrong(ptr [[S_ADDR]], ptr null) + +// Thunk function should not release anything even with ns_consumed +// CHECK-LABEL: define hidden double @"\01-[Shape distanceFrom:]" +// CHECK-NOT: call void @llvm.objc.storeStrong +// CHECK-LABEL: } +- (double) distanceFrom:(Shape *) __attribute__((ns_consumed)) s __attribute__((objc_direct)) { + double dist = sqrt((s.x - self.x) * (s.x - self.x) + (s.y - self.y) * (s.y - self.y)); + return dist; +} +@end + +// CHECK-LABEL: define i32 @main +int main(int argc, char** argv) { // argv = ["1", "2", "3", "4"] +@autoreleasepool { + // CHECK-EXE: Alloc + Shape* classDefault = [Shape default]; + // CHECK-EXE-NEXT: Alloc + Shape* s = [[Shape alloc] initWithX:atoi(argv[0]) Y:atoi(argv[1])]; + // CHECK-EXE-NEXT: Alloc + Shape* t = [[Shape alloc] initWithX:atoi(argv[2]) Y:atoi(argv[3])]; + // CHECK-EXE-NEXT: Alloc + Shape* zero = [[Shape alloc] initDefault]; + // CHECK-EXE-NEXT: Alloc + Shape* anotherDefault = [Shape default]; + + // CHECK: [[CALL_CLONE:%.*]] = call ptr @"\01-[Shape clone]" + // CHECK-NEXT: call void asm sideeffect "mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue", ""() + // CHECK-NEXT: {{%.*}} = call ptr @llvm.objc.retainAutoreleasedReturnValue(ptr [[CALL_CLONE]]) + + // CHECK-EXE-NEXT: Alloc [[CLOND_ID:.*]] + Shape* cloned = [s clone]; + + Shape* null = nil; + // CHECK-EXE: Dist: 2.82 + printf("Dist: %lf\n", [s distanceFrom:t]); + // CHECK-EXE-NEXT: Dist: 3.60 + printf("Dist: %lf\n", [zero distanceFrom:t]); + // CHECK-EXE-NEXT: Dist: 3.60 + printf("Dist: %lf\n", [classDefault distanceFrom:t]); + // CHECK-EXE-NEXT: Dist: 0.00 + printf("Dist: %lf\n", [s distanceFrom:s]); + // CHECK-EXE-NEXT: Dist: 0.00 + printf("Dist: %lf\n", [classDefault distanceFrom:anotherDefault]); + // CHECK-EXE-NEXT: Dist: 0.00 + printf("Dist: %lf\n", [null distanceFrom:zero]); + // CHECK-EXE-NEXT: Dist: 0.00 + printf("Dist: %lf\n", [s distanceFrom:cloned]); + + // Cloned object should be released as well + // CHECK-EXE: Dealloc [[CLOND_ID]] +} + return 0; +} diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m new file mode 100644 index 0000000000000..11b37ea3537fa --- /dev/null +++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m @@ -0,0 +1,56 @@ +// REQUIRES: system-darwin + +// RUN: %clang -fobjc-emit-nil-check-thunk \ +// RUN: -target arm64-apple-darwin -fobjc-arc \ +// RUN: -O0 -S -emit-llvm %s -o - | FileCheck %s + +#import + +@interface Shape: NSObject +@property(direct) int x; +@property(direct) int y; +- (instancetype)initWithX:(int)x Y:(int)y __attribute__((objc_direct)); +- (void) move: (Shape *) __attribute__((ns_consumed)) s __attribute__((objc_direct)); ++ (Shape*) default __attribute__((objc_direct)); +@end + +@implementation Shape +- (instancetype)initWithX:(int)x Y:(int)y { + if (self = [super init]) { + _x = x; + _y = y; + } + return self; +} + +// Inner function should +// 1. Call inner set (because we alreaday know self is not null) +// 2. Call thunk get (because we don't know if s is null) +// 3. Release s. +// CHECK-LABEL: define hidden void @"\01-[Shape move:]_nonnull" +// CHECK: {{.*}} = call i32 @"\01-[Shape x]" +// CHECK: call void @"\01-[Shape setX:]_nonnull" +// CHECK: {{.*}} = call i32 @"\01-[Shape y]" +// CHECK: call void @"\01-[Shape setY:]_nonnull" +// CHECK: call void @llvm.objc.storeStrong +// CHECK-LABEL: } + +// Outer function should not release anything. +// CHECK-LABEL: define hidden void @"\01-[Shape move:]" +// CHECK-NOT: call void @llvm.objc.storeStrong +// CHECK-LABEL: } +- (void) move: (Shape *) s { + self.x = s.x; + self.y = s.y; +} + ++ (Shape*) default { + return [[Shape alloc] initWithX:1 Y:1]; +} +@end + +int main() { + Shape *s = [Shape default]; + Shape *t = nil; + [t move:s]; +} diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m new file mode 100644 index 0000000000000..6896673bf3179 --- /dev/null +++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m @@ -0,0 +1,68 @@ +// This file checks that certain nil checks can be removed from direct method calls. + +// RUN: %clang_cc1 -fobjc-emit-nil-check-thunk -O0 -emit-llvm -fobjc-arc -triple arm64-apple-darwin %s -o - | FileCheck %s +// RUN: %clang_cc1 -fobjc-emit-nil-check-thunk -O2 -emit-llvm -fobjc-arc -triple arm64-apple-darwin %s -o - | FileCheck %s --check-prefixes=OPT + +__attribute__((objc_root_class)) +@interface Root +@property(direct) int idx; +- (int)privateLoadWithOffset:(int *)ptr __attribute__((objc_direct)); +@end + +// Optimization is enabled because the source code is available, and the compiler can reason that some methods don't require nil checks. +@interface Fib : Root +- (int)fibWithN:(int)n __attribute__((objc_direct)); +@end + +@implementation Fib +// With optimization, the inner function call should be a tail call. +// OPT-LABEL: define hidden i32 @"\01-[Fib fibWithN:]_nonnull" +// OPT: {{.*}} tail call i32 @"\01-[Fib fibWithN:]_nonnull" + +// The inner function knows that self is non null so it can call the method without the nil check. +// CHECK-LABEL: define hidden i32 @"\01-[Fib fibWithN:]_nonnull" +// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_nonnull" +// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_nonnull" + +// Thunk function calls the inner function as usual. +// CHECK-LABEL: define hidden i32 @"\01-[Fib fibWithN:]" +// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_nonnull" +- (int)fibWithN:(int)n { + if (n <= 0) return 0; + if (n == 1) return 1; + return [self fibWithN:n-1] + [self fibWithN:n-2]; +} +@end + +@interface SubRoot : Root +@property(direct) int val; + +- (int)calculateWithPtr:(int*)ptr __attribute__((objc_direct)); +- (int)privateMethod:(int)n __attribute__((objc_direct)); +@end +@implementation SubRoot +- (int)calculateWithPtr:(int*)ptr { + // For inner functions, it is trivial to reason that the receiver `self` can't be null + // CHECK-LABEL: define hidden i32 @"\01-[SubRoot calculateWithPtr:]_nonnull" + // CHECK: {{.*}} = call i32 @"\01-[SubRoot val]_nonnull" + // CHECK: call void @"\01-[SubRoot setVal:]_nonnull" + // CHECK: {{.*}} = call i32 @"\01-[Root privateLoadWithOffset:]_nonnull" + // CHECK: {{.*}} = call i32 @"\01-[SubRoot privateMethod:]_nonnull" + // CHECK: {{.*}} = call i32 @"\01-[Root idx]_nonnull" + // CHECK: call void @"\01-[Root setIdx:]_nonnull" + int ret = [self val]; + [self setVal:*ptr]; + ret += [self privateLoadWithOffset:ptr]; + ret += [self privateMethod:ret]; + ret += [self idx]; + [self setIdx:ret]; + return ret; +} +@end + +// The thunk declarations don't exist since all calls to them are non null. +// We trust that these symbols will be generated when the definition is available. +// CHECK-LABEL: declare i32 @"\01-[Root privateLoadWithOffset:]_nonnull"(ptr, ptr) +// CHECK-LABEL: declare i32 @"\01-[SubRoot privateMethod:]_nonnull"(ptr, i32) +// CHECK-LABEL: declare i32 @"\01-[Root idx]_nonnull"(ptr) +// CHECK-LABEL: declare void @"\01-[Root setIdx:]_nonnull"(ptr, i32) diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m new file mode 100644 index 0000000000000..85d5b838cbb4b --- /dev/null +++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m @@ -0,0 +1,62 @@ +// RUN: %clang -fobjc-emit-nil-check-thunk \ +// RUN: -target arm64-apple-darwin -fobjc-arc \ +// RUN: -O0 -S -emit-llvm %s -o - | FileCheck %s + + +#include + +int vprintf(const char * restrict format, va_list ap); +#define NULL ((void *)0) + +__attribute__((objc_root_class)) +@interface Root +- (void)printWithFormat:(const char *)format, ... __attribute__((objc_direct, visibility("default"))); +- (void)vprintWithFormat:(const char *)format Args:(va_list) args __attribute__((objc_direct, visibility("default"))); +@end + +@implementation Root +// CHECK-LABEL: define {{.*}} void @"\01-[Root printWithFormat:]" +- (void)printWithFormat:(const char *)format, ... { + // Inner functions won't be called since var arg functions don't have a thunk. + // CHECK: call void (ptr, ptr, ...) @"\01-[Root printWithFormat:]" + // CHECK: call void (ptr, ptr, ...) @"\01-[Root printWithFormat:]" + [self printWithFormat:format, "Hello World"]; + [self printWithFormat:format, "!", 1, 2.0]; + va_list args; + // CHECK: call void @llvm.va_start + va_start(args, format); + // CHECK: call void @"\01-[Root vprintWithFormat:Args:]_nonnull" + [self vprintWithFormat:format Args:args]; + // CHECK: call void @llvm.va_end + va_end(args); +} +// CHECK-NOT: