Index: include/clang/Basic/ObjCRuntime.h =================================================================== --- include/clang/Basic/ObjCRuntime.h +++ include/clang/Basic/ObjCRuntime.h @@ -171,6 +171,79 @@ llvm_unreachable("bad kind"); } + /// Does this runtime provide ARC entrypoints that are likely to be faster + /// than an ordinary message send of the appropriate selector? + /// + /// The ARC entrypoints are guaranteed to be equivalent to just sending the + /// corresponding message. If the entrypoint is implemented naively as just a + /// message send, using it is a trade-off: it sacrifices a few cycles of + /// overhead to save a small amount of code. However, it's possible for + /// runtimes to detect and special-case classes that use "standard" + /// retain/release behavior; if that's dynamically a large proportion of all + /// retained objects, using the entrypoint will also be faster than using a + /// message send. + /// + /// When this method returns true, Clang will turn non-super message sends of + /// certain selectors into calls to the correspond entrypoint: + /// retain => objc_retain + /// release => objc_release + bool shouldUseARCFunctionsForRetainRelease() const { + switch (getKind()) { + case FragileMacOSX: + return false; + case MacOSX: + return getVersion() >= VersionTuple(10, 10); + case iOS: + return getVersion() >= VersionTuple(8); + case WatchOS: + return true; + + case GCC: + return false; + case GNUstep: + return false; + case ObjFW: + return false; + } + llvm_unreachable("bad kind"); + } + + /// Does this runtime provide entrypoints that are likely to be faster + /// than an ordinary message send of the "alloc" selector? + /// + /// The "alloc" entrypoint is guaranteed to be equivalent to just sending the + /// corresponding message. If the entrypoint is implemented naively as just a + /// message send, using it is a trade-off: it sacrifices a few cycles of + /// overhead to save a small amount of code. However, it's possible for + /// runtimes to detect and special-case classes that use "standard" + /// alloc behavior; if that's dynamically a large proportion of all + /// objects, using the entrypoint will also be faster than using a message + /// send. + /// + /// When this method returns true, Clang will turn non-super message sends of + /// certain selectors into calls to the correspond entrypoint: + /// alloc => objc_alloc + bool shouldUseRuntimeFunctionsForAlloc() const { + switch (getKind()) { + case FragileMacOSX: + return false; + case MacOSX: + return getVersion() >= VersionTuple(10, 10); + case iOS: + return getVersion() >= VersionTuple(8); + case WatchOS: + return true; + + case GCC: + return false; + case GNUstep: + return false; + case ObjFW: + return false; + } + llvm_unreachable("bad kind"); + } + /// \brief Does this runtime supports optimized setter entrypoints? bool hasOptimizedSetter() const { switch (getKind()) { Index: include/clang/Driver/Options.td =================================================================== --- include/clang/Driver/Options.td +++ include/clang/Driver/Options.td @@ -892,6 +892,10 @@ def fobjc_arc : Flag<["-"], "fobjc-arc">, Group, Flags<[CC1Option]>, HelpText<"Synthesize retain and release calls for Objective-C pointers">; def fno_objc_arc : Flag<["-"], "fno-objc-arc">, Group; +def fobjc_convert_messages_to_runtime_calls : + Flag<["-"], "fobjc-convert-messages-to-runtime-calls">, Group; +def fno_objc_convert_messages_to_runtime_calls : + Flag<["-"], "fno-objc-convert-messages-to-runtime-calls">, Group, Flags<[CC1Option]>; def fobjc_arc_exceptions : Flag<["-"], "fobjc-arc-exceptions">, Group, Flags<[CC1Option]>, HelpText<"Use EH-safe code when synthesizing retains and releases in -fobjc-arc">; def fno_objc_arc_exceptions : Flag<["-"], "fno-objc-arc-exceptions">, Group; Index: include/clang/Frontend/CodeGenOptions.def =================================================================== --- include/clang/Frontend/CodeGenOptions.def +++ include/clang/Frontend/CodeGenOptions.def @@ -95,6 +95,8 @@ CODEGENOPT(NoZeroInitializedInBSS , 1, 0) ///< -fno-zero-initialized-in-bss. /// \brief Method of Objective-C dispatch to use. ENUM_CODEGENOPT(ObjCDispatchMethod, ObjCDispatchMethodKind, 2, Legacy) +/// Replace certain message sends with calls to ObjC runtime entrypoints +CODEGENOPT(ObjCConvertMessagesToRuntimeCalls , 1, 1) CODEGENOPT(OmitLeafFramePointer , 1, 0) ///< Set when -momit-leaf-frame-pointer is ///< enabled. VALUE_CODEGENOPT(OptimizationLevel, 2, 0) ///< The -O[0-3] option specified. Index: lib/CodeGen/CGObjC.cpp =================================================================== --- lib/CodeGen/CGObjC.cpp +++ lib/CodeGen/CGObjC.cpp @@ -338,6 +338,69 @@ return nullptr; } +/// The ObjC runtime may provide entrypoints that are likely to be faster +/// than an ordinary message send of the appropriate selector. +/// +/// The entrypoints are guaranteed to be equivalent to just sending the +/// corresponding message. If the entrypoint is implemented naively as just a +/// message send, using it is a trade-off: it sacrifices a few cycles of +/// overhead to save a small amount of code. However, it's possible for +/// runtimes to detect and special-case classes that use "standard" +/// retain/release behavior; if that's dynamically a large proportion of all +/// retained objects, using the entrypoint will also be faster than using a +/// message send. +/// +/// If the runtime does support a required entrypoint, then this method will +/// generate a call and return the resulting value. Otherwise it will return +/// None and the caller can generate a msgSend instead. +static Optional +tryGenerateSpecializedMessageSend(CodeGenFunction &CGF, QualType ResultType, + llvm::Value *Receiver, Selector Sel, + const ObjCMethodDecl *method) { + auto &CGM = CGF.CGM; + if (!CGM.getCodeGenOpts().ObjCConvertMessagesToRuntimeCalls) + return None; + + auto &Runtime = CGM.getLangOpts().ObjCRuntime; + switch (Sel.getMethodFamily()) { + case OMF_alloc: + // Make sure the name is exactly 'alloc'. All methods with that + // prefix are identified as OMF_alloc but we only want to call the + // runtime for this version. + if (Runtime.shouldUseRuntimeFunctionsForAlloc() && Sel.isUnarySelector() && + Sel.getNameForSlot(0) == "alloc" && + ResultType->isObjCObjectPointerType()) + return CGF.EmitObjCAlloc(Receiver); + break; + + case OMF_autorelease: + if (ResultType->isObjCObjectPointerType() && + CGM.getLangOpts().getGC() == LangOptions::NonGC && + Runtime.shouldUseARCFunctionsForRetainRelease()) + return CGF.EmitARCAutorelease(Receiver); + break; + + case OMF_retain: + if (ResultType->isObjCObjectPointerType() && + CGM.getLangOpts().getGC() == LangOptions::NonGC && + Runtime.shouldUseARCFunctionsForRetainRelease()) + return CGF.EmitARCRetainNonBlock(Receiver); + break; + + case OMF_release: + if (ResultType->isVoidType() && + CGM.getLangOpts().getGC() == LangOptions::NonGC && + Runtime.shouldUseARCFunctionsForRetainRelease()) { + CGF.EmitARCRelease(Receiver, ARCPreciseLifetime); + return nullptr; + } + break; + default: + break; + } + return None; +} + RValue CodeGenFunction::EmitObjCMessageExpr(const ObjCMessageExpr *E, ReturnValueSlot Return) { // Only the lookup mechanism and first two arguments of the method @@ -460,10 +523,16 @@ Args, method); } else { - result = Runtime.GenerateMessageSend(*this, Return, ResultType, - E->getSelector(), - Receiver, Args, OID, - method); + // Call runtime methods directly if we can. + if (Optional SpecializedResult = + tryGenerateSpecializedMessageSend(*this, ResultType, Receiver, + E->getSelector(), method)) { + result = RValue::get(SpecializedResult.getValue()); + } else { + result = Runtime.GenerateMessageSend(*this, Return, ResultType, + E->getSelector(), Receiver, Args, + OID, method); + } } // For delegate init calls in ARC, implicitly store the result of @@ -2333,6 +2402,13 @@ return InitRV.getScalarVal(); } +/// Allocate the given objc object. +/// call i8* \@objc_alloc(i8* %value) +llvm::Value *CodeGenFunction::EmitObjCAlloc(llvm::Value *value) { + return emitARCValueOperation( + *this, value, CGM.getObjCEntrypoints().objc_alloc, "objc_alloc"); +} + /// Produce the code to do a primitive release. /// [tmp drain]; void CodeGenFunction::EmitObjCMRRAutoreleasePoolPop(llvm::Value *Arg) { Index: lib/CodeGen/CodeGenFunction.h =================================================================== --- lib/CodeGen/CodeGenFunction.h +++ lib/CodeGen/CodeGenFunction.h @@ -2799,6 +2799,7 @@ std::pair EmitARCStoreStrong(const BinaryOperator *e, bool ignored); + llvm::Value *EmitObjCAlloc(llvm::Value *value); llvm::Value *EmitObjCThrowOperand(const Expr *expr); llvm::Value *EmitObjCConsumeObject(QualType T, llvm::Value *Ptr); llvm::Value *EmitObjCExtendObjectLifetime(QualType T, llvm::Value *Ptr); Index: lib/CodeGen/CodeGenModule.h =================================================================== --- lib/CodeGen/CodeGenModule.h +++ lib/CodeGen/CodeGenModule.h @@ -111,7 +111,10 @@ struct ObjCEntrypoints { ObjCEntrypoints() { memset(this, 0, sizeof(*this)); } - /// void objc_autoreleasePoolPop(void*); + /// void objc_alloc(id); + llvm::Constant *objc_alloc; + + /// void objc_autoreleasePoolPop(void*); llvm::Constant *objc_autoreleasePoolPop; /// void *objc_autoreleasePoolPush(void); Index: lib/Driver/Tools.cpp =================================================================== --- lib/Driver/Tools.cpp +++ lib/Driver/Tools.cpp @@ -4901,6 +4901,18 @@ } + // Allow the user to control whether messages can be converted to runtime + // functions. + if (types::isObjC(InputType)) { + auto *Arg = Args.getLastArg( + options::OPT_fobjc_convert_messages_to_runtime_calls, + options::OPT_fno_objc_convert_messages_to_runtime_calls); + if (Arg && + Arg->getOption().matches( + options::OPT_fno_objc_convert_messages_to_runtime_calls)) + CmdArgs.push_back("-fno-objc-convert-messages-to-runtime-calls"); + } + // -fobjc-infer-related-result-type is the default, except in the Objective-C // rewriter. if (rewriteKind != RK_None) Index: lib/Frontend/CompilerInvocation.cpp =================================================================== --- lib/Frontend/CompilerInvocation.cpp +++ lib/Frontend/CompilerInvocation.cpp @@ -609,6 +609,9 @@ } } + if (Args.hasArg(OPT_fno_objc_convert_messages_to_runtime_calls)) + Opts.ObjCConvertMessagesToRuntimeCalls = 0; + Opts.EmulatedTLS = Args.hasFlag(OPT_femulated_tls, OPT_fno_emulated_tls, false); Index: test/CodeGenObjC/convert-messages-to-runtime-calls.m =================================================================== --- /dev/null +++ test/CodeGenObjC/convert-messages-to-runtime-calls.m @@ -0,0 +1,80 @@ +// RUN: %clang_cc1 -fobjc-runtime=macosx-10.10.0 -emit-llvm -o - %s -fno-objc-convert-messages-to-runtime-calls | FileCheck %s --check-prefix=MSGS +// RUN: %clang_cc1 -fobjc-runtime=macosx-10.10.0 -emit-llvm -o - %s | FileCheck %s --check-prefix=CALLS +// RUN: %clang_cc1 -fobjc-runtime=macosx-10.9.0 -emit-llvm -o - %s | FileCheck %s --check-prefix=MSGS +// RUN: %clang_cc1 -fobjc-runtime=macosx-fragile-10.10.0 -emit-llvm -o - %s | FileCheck %s --check-prefix=MSGS +// Make sure we don't do calls to retain/release when using GC. +// RUN: %clang_cc1 -fobjc-runtime=macosx-10.10.0 -emit-llvm -o - %s -fobjc-gc | FileCheck %s --check-prefix=GC +// RUN: %clang_cc1 -fobjc-runtime=ios-8.0 -emit-llvm -o - %s | FileCheck %s --check-prefix=CALLS +// RUN: %clang_cc1 -fobjc-runtime=ios-7.0 -emit-llvm -o - %s | FileCheck %s --check-prefix=MSGS +// Note: This line below is for tvos for which the driver passes through to use the ios9.0 runtime. +// RUN: %clang_cc1 -fobjc-runtime=ios-9.0 -emit-llvm -o - %s | FileCheck %s --check-prefix=CALLS +// RUN: %clang_cc1 -fobjc-runtime=watchos-2.0 -emit-llvm -o - %s | FileCheck %s --check-prefix=CALLS + +@interface NSObject ++ (id)alloc; ++ (id)alloc2; +- (id)init; +- (id)retain; +- (void)release; +- (NSObject *)autorelease; +@end + +@interface NSString : NSObject +- (void)retain_self; +- (void)retain_super; +@end + +// CHECK-LABEL: define {{.*}}void @test1 +NSObject *test1(id x) { + // MSGS: {{call.*@objc_msgSend}} + // MSGS: {{call.*@objc_msgSend}} + // MSGS: {{call.*@objc_msgSend}} + // MSGS: {{call.*@objc_msgSend}} + // CALLS: {{call.*@objc_alloc}} + // CALLS: {{call.*@objc_retain}} + // CALLS: {{call.*@objc_release}} + // CALLS: {{call.*@objc_autorelease}} + // CALLS-NEXT: bitcast i8* + // CALLS-NEXT: ret + // GC: {{call.*@objc_alloc}} + // GC: {{call.*@objc_msgSend}} + // GC: {{call.*@objc_msgSend}} + // GC: {{call.*@objc_msgSend}} + [NSObject alloc]; + [x retain]; + [x release]; + // The auto release returns its value here as then we can ensure we insert + // the bitcasts as needed. + return [x autorelease]; +} + +// CHECK-LABEL: define {{.*}}void @test2 +void test2() { + // MSGS: {{call.*@objc_msgSend}} + // CALLS: {{call.*@objc_msgSend}} + // GC: {{call.*@objc_msgSend}} + // Make sure alloc has the correct name and number of types. + [NSObject alloc2]; +} + +@implementation NSString + +// Make sure we can convert a message to a dynamic receiver to a call +// CHECK-LABEL: define {{.*}}void @retain_self +- (void)retain_self { + // MSGS: {{call.*@objc_msgSend}} + // CALLS: {{call.*@objc_retain}} + // GC: {{call.*@objc_msgSend}} + [self retain]; +} + +// Make sure we never convert a message to super to a call +// CHECK-LABEL: define {{.*}}void @retain_super +- (void)retain_super { + // MSGS: {{call.*@objc_msgSend}} + // CALLS: {{call.*@objc_msgSend}} + // GC: {{call.*@objc_msgSend}} + [super retain]; +} + +@end \ No newline at end of file Index: test/Driver/objc-convert-messages-to-runtime-calls.m =================================================================== --- /dev/null +++ test/Driver/objc-convert-messages-to-runtime-calls.m @@ -0,0 +1,7 @@ +// RUN: %clang %s -### -o %t.o 2>&1 -fsyntax-only -fobjc-convert-messages-to-runtime-calls -fno-objc-convert-messages-to-runtime-calls -target x86_64-apple-macosx10.10.0 | FileCheck %s --check-prefix=DISABLE +// RUN: %clang %s -### -o %t.o 2>&1 -fsyntax-only -fno-objc-convert-messages-to-runtime-calls -fobjc-convert-messages-to-runtime-calls -target x86_64-apple-macosx10.10.0 | FileCheck %s --check-prefix=ENABLE + +// Check that we pass fobjc-convert-messages-to-runtime-calls only when supported, and not explicitly disabled. + +// DISABLE: "-fno-objc-convert-messages-to-runtime-calls" +// ENABLE-NOT: "-fno-objc-convert-messages-to-runtime-calls"