From bfa6f5fe991a922e28ed05263ed795b8fb058b4b Mon Sep 17 00:00:00 2001 From: Pete Cooper Date: Sat, 8 Dec 2018 05:13:50 +0000 Subject: [PATCH] Convert some ObjC msgSends to runtime calls. It is faster to directly call the ObjC runtime for methods such as alloc/allocWithZone instead of sending a message to those functions. This patch adds support for converting messages to alloc/allocWithZone to their equivalent runtime calls. Tests included for the positive case of applying this transformation, negative tests that we ensure we only convert "alloc" to objc_alloc, not "alloc2", and also a driver test to ensure we enable this only for supported runtime versions. Reviewed By: rjmccall https://reviews.llvm.org/D55349 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@348687 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/ObjCRuntime.h | 37 +++++++ include/clang/Driver/Options.td | 4 + include/clang/Frontend/CodeGenOptions.def | 2 + lib/CodeGen/CGObjC.cpp | 101 +++++++++++++++--- lib/CodeGen/CodeGenFunction.h | 4 + lib/CodeGen/CodeGenModule.h | 8 +- lib/Driver/ToolChains/Clang.cpp | 12 +++ lib/Frontend/CompilerInvocation.cpp | 4 + .../convert-messages-to-runtime-calls.m | 80 ++++++++++++++ .../objc-convert-messages-to-runtime-calls.m | 7 ++ 10 files changed, 245 insertions(+), 14 deletions(-) create mode 100644 test/CodeGenObjC/convert-messages-to-runtime-calls.m create mode 100644 test/Driver/objc-convert-messages-to-runtime-calls.m diff --git a/include/clang/Basic/ObjCRuntime.h b/include/clang/Basic/ObjCRuntime.h index c307c96433..f8b4826d3d 100644 --- a/include/clang/Basic/ObjCRuntime.h +++ b/include/clang/Basic/ObjCRuntime.h @@ -173,6 +173,43 @@ public: 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 corresponding entrypoint: + /// alloc => objc_alloc + /// allocWithZone:nil => objc_allocWithZone + 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"); + } + /// Does this runtime supports optimized setter entrypoints? bool hasOptimizedSetter() const { switch (getKind()) { diff --git a/include/clang/Driver/Options.td b/include/clang/Driver/Options.td index cde7d80507..0a96820734 100644 --- a/include/clang/Driver/Options.td +++ b/include/clang/Driver/Options.td @@ -1473,6 +1473,10 @@ def fno_zero_initialized_in_bss : Flag<["-"], "fno-zero-initialized-in-bss">, Gr 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; diff --git a/include/clang/Frontend/CodeGenOptions.def b/include/clang/Frontend/CodeGenOptions.def index e531bb80ff..952aa588b6 100644 --- a/include/clang/Frontend/CodeGenOptions.def +++ b/include/clang/Frontend/CodeGenOptions.def @@ -149,6 +149,8 @@ CODEGENOPT(UniformWGSize , 1, 0) ///< -cl-uniform-work-group-size CODEGENOPT(NoZeroInitializedInBSS , 1, 0) ///< -fno-zero-initialized-in-bss. /// 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. diff --git a/lib/CodeGen/CGObjC.cpp b/lib/CodeGen/CGObjC.cpp index e7b0b82bc5..28b843e1cd 100644 --- a/lib/CodeGen/CGObjC.cpp +++ b/lib/CodeGen/CGObjC.cpp @@ -352,6 +352,56 @@ static const Expr *findWeakLValue(const Expr *E) { 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" +/// behavior; if that's dynamically a large proportion of all 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, + const CallArgList& Args, 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: + if (Runtime.shouldUseRuntimeFunctionsForAlloc() && + ResultType->isObjCObjectPointerType()) { + // [Foo alloc] -> objc_alloc(Foo) + if (Sel.isUnarySelector() && Sel.getNameForSlot(0) == "alloc") + return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType)); + // [Foo allocWithZone:nil] -> objc_allocWithZone(Foo) + if (Sel.isKeywordSelector() && Sel.getNumArgs() == 1 && + Args.size() == 1 && Args.front().getType()->isPointerType() && + Sel.getNameForSlot(0) == "allocWithZone") { + const llvm::Value* arg = Args.front().getKnownRValue().getScalarVal(); + if (isa(arg)) + return CGF.EmitObjCAllocWithZone(Receiver, + CGF.ConvertType(ResultType)); + return None; + } + } + break; + + default: + break; + } + return None; +} + RValue CodeGenFunction::EmitObjCMessageExpr(const ObjCMessageExpr *E, ReturnValueSlot Return) { // Only the lookup mechanism and first two arguments of the method @@ -474,10 +524,16 @@ RValue CodeGenFunction::EmitObjCMessageExpr(const ObjCMessageExpr *E, 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, Args, + 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 @@ -1845,6 +1901,7 @@ static llvm::Constant *createARCRuntimeFunction(CodeGenModule &CGM, /// where a null input causes a no-op and returns null. static llvm::Value *emitARCValueOperation(CodeGenFunction &CGF, llvm::Value *value, + llvm::Type *returnType, llvm::Constant *&fn, StringRef fnName, bool isTailCall = false) { @@ -1858,7 +1915,7 @@ static llvm::Value *emitARCValueOperation(CodeGenFunction &CGF, } // Cast the argument to 'id'. - llvm::Type *origType = value->getType(); + llvm::Type *origType = returnType ? returnType : value->getType(); value = CGF.Builder.CreateBitCast(value, CGF.Int8PtrTy); // Call the function. @@ -1964,7 +2021,7 @@ llvm::Value *CodeGenFunction::EmitARCRetain(QualType type, llvm::Value *value) { /// Retain the given object, with normal retain semantics. /// call i8* \@objc_retain(i8* %value) llvm::Value *CodeGenFunction::EmitARCRetainNonBlock(llvm::Value *value) { - return emitARCValueOperation(*this, value, + return emitARCValueOperation(*this, value, nullptr, CGM.getObjCEntrypoints().objc_retain, "objc_retain"); } @@ -1978,7 +2035,7 @@ llvm::Value *CodeGenFunction::EmitARCRetainNonBlock(llvm::Value *value) { llvm::Value *CodeGenFunction::EmitARCRetainBlock(llvm::Value *value, bool mandatory) { llvm::Value *result - = emitARCValueOperation(*this, value, + = emitARCValueOperation(*this, value, nullptr, CGM.getObjCEntrypoints().objc_retainBlock, "objc_retainBlock"); @@ -2048,7 +2105,7 @@ static void emitAutoreleasedReturnValueMarker(CodeGenFunction &CGF) { llvm::Value * CodeGenFunction::EmitARCRetainAutoreleasedReturnValue(llvm::Value *value) { emitAutoreleasedReturnValueMarker(*this); - return emitARCValueOperation(*this, value, + return emitARCValueOperation(*this, value, nullptr, CGM.getObjCEntrypoints().objc_retainAutoreleasedReturnValue, "objc_retainAutoreleasedReturnValue"); } @@ -2063,7 +2120,7 @@ CodeGenFunction::EmitARCRetainAutoreleasedReturnValue(llvm::Value *value) { llvm::Value * CodeGenFunction::EmitARCUnsafeClaimAutoreleasedReturnValue(llvm::Value *value) { emitAutoreleasedReturnValueMarker(*this); - return emitARCValueOperation(*this, value, + return emitARCValueOperation(*this, value, nullptr, CGM.getObjCEntrypoints().objc_unsafeClaimAutoreleasedReturnValue, "objc_unsafeClaimAutoreleasedReturnValue"); } @@ -2178,7 +2235,7 @@ llvm::Value *CodeGenFunction::EmitARCStoreStrong(LValue dst, /// Autorelease the given object. /// call i8* \@objc_autorelease(i8* %value) llvm::Value *CodeGenFunction::EmitARCAutorelease(llvm::Value *value) { - return emitARCValueOperation(*this, value, + return emitARCValueOperation(*this, value, nullptr, CGM.getObjCEntrypoints().objc_autorelease, "objc_autorelease"); } @@ -2187,7 +2244,7 @@ llvm::Value *CodeGenFunction::EmitARCAutorelease(llvm::Value *value) { /// call i8* \@objc_autoreleaseReturnValue(i8* %value) llvm::Value * CodeGenFunction::EmitARCAutoreleaseReturnValue(llvm::Value *value) { - return emitARCValueOperation(*this, value, + return emitARCValueOperation(*this, value, nullptr, CGM.getObjCEntrypoints().objc_autoreleaseReturnValue, "objc_autoreleaseReturnValue", /*isTailCall*/ true); @@ -2197,7 +2254,7 @@ CodeGenFunction::EmitARCAutoreleaseReturnValue(llvm::Value *value) { /// call i8* \@objc_retainAutoreleaseReturnValue(i8* %value) llvm::Value * CodeGenFunction::EmitARCRetainAutoreleaseReturnValue(llvm::Value *value) { - return emitARCValueOperation(*this, value, + return emitARCValueOperation(*this, value, nullptr, CGM.getObjCEntrypoints().objc_retainAutoreleaseReturnValue, "objc_retainAutoreleaseReturnValue", /*isTailCall*/ true); @@ -2226,7 +2283,7 @@ llvm::Value *CodeGenFunction::EmitARCRetainAutorelease(QualType type, /// call i8* \@objc_retainAutorelease(i8* %value) llvm::Value * CodeGenFunction::EmitARCRetainAutoreleaseNonBlock(llvm::Value *value) { - return emitARCValueOperation(*this, value, + return emitARCValueOperation(*this, value, nullptr, CGM.getObjCEntrypoints().objc_retainAutorelease, "objc_retainAutorelease"); } @@ -2385,6 +2442,24 @@ llvm::Value *CodeGenFunction::EmitObjCMRRAutoreleasePoolPush() { return InitRV.getScalarVal(); } +/// Allocate the given objc object. +/// call i8* \@objc_alloc(i8* %value) +llvm::Value *CodeGenFunction::EmitObjCAlloc(llvm::Value *value, + llvm::Type *resultType) { + return emitARCValueOperation(*this, value, resultType, + CGM.getObjCEntrypoints().objc_alloc, + "objc_alloc"); +} + +/// Allocate the given objc object. +/// call i8* \@objc_allocWithZone(i8* %value) +llvm::Value *CodeGenFunction::EmitObjCAllocWithZone(llvm::Value *value, + llvm::Type *resultType) { + return emitARCValueOperation(*this, value, resultType, + CGM.getObjCEntrypoints().objc_allocWithZone, + "objc_allocWithZone"); +} + /// Produce the code to do a primitive release. /// [tmp drain]; void CodeGenFunction::EmitObjCMRRAutoreleasePoolPop(llvm::Value *Arg) { diff --git a/lib/CodeGen/CodeGenFunction.h b/lib/CodeGen/CodeGenFunction.h index ab7305a112..9b802991e7 100644 --- a/lib/CodeGen/CodeGenFunction.h +++ b/lib/CodeGen/CodeGenFunction.h @@ -3805,6 +3805,10 @@ public: std::pair EmitARCStoreUnsafeUnretained(const BinaryOperator *e, bool ignored); + llvm::Value *EmitObjCAlloc(llvm::Value *value, + llvm::Type *returnType); + llvm::Value *EmitObjCAllocWithZone(llvm::Value *value, + llvm::Type *returnType); llvm::Value *EmitObjCThrowOperand(const Expr *expr); llvm::Value *EmitObjCConsumeObject(QualType T, llvm::Value *Ptr); llvm::Value *EmitObjCExtendObjectLifetime(QualType T, llvm::Value *Ptr); diff --git a/lib/CodeGen/CodeGenModule.h b/lib/CodeGen/CodeGenModule.h index fe42b8beb9..a97cce3a55 100644 --- a/lib/CodeGen/CodeGenModule.h +++ b/lib/CodeGen/CodeGenModule.h @@ -119,7 +119,13 @@ struct OrderGlobalInits { struct ObjCEntrypoints { ObjCEntrypoints() { memset(this, 0, sizeof(*this)); } - /// void objc_autoreleasePoolPop(void*); + /// void objc_alloc(id); + llvm::Constant *objc_alloc; + + /// void objc_allocWithZone(id); + llvm::Constant *objc_allocWithZone; + + /// void objc_autoreleasePoolPop(void*); llvm::Constant *objc_autoreleasePoolPop; /// void *objc_autoreleasePoolPush(void); diff --git a/lib/Driver/ToolChains/Clang.cpp b/lib/Driver/ToolChains/Clang.cpp index bd04317966..eaaa1bbff2 100644 --- a/lib/Driver/ToolChains/Clang.cpp +++ b/lib/Driver/ToolChains/Clang.cpp @@ -2866,6 +2866,18 @@ static void RenderObjCOptions(const ToolChain &TC, const Driver &D, Args.ClaimAllArgs(options::OPT_fno_objc_arc_exceptions); } + // Allow the user to control whether messages can be converted to runtime + // functions. + if (types::isObjC(Input.getType())) { + 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 (InferCovariantReturns) diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 1eeb9732fd..02c653f91b 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -1155,6 +1155,10 @@ static bool ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, InputKind IK, } } + + if (Args.hasArg(OPT_fno_objc_convert_messages_to_runtime_calls)) + Opts.ObjCConvertMessagesToRuntimeCalls = 0; + if (Args.getLastArg(OPT_femulated_tls) || Args.getLastArg(OPT_fno_emulated_tls)) { Opts.ExplicitEmulatedTLS = true; diff --git a/test/CodeGenObjC/convert-messages-to-runtime-calls.m b/test/CodeGenObjC/convert-messages-to-runtime-calls.m new file mode 100644 index 0000000000..0a018204f3 --- /dev/null +++ b/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 +// 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 + +#define nil (id)0 + +@interface NSObject ++ (id)alloc; ++ (id)allocWithZone:(void*)zone; ++ (id)alloc2; +@end + +// CHECK-LABEL: define {{.*}}void @test1 +void test1(id x) { + // MSGS: {{call.*@objc_msgSend}} + // MSGS: {{call.*@objc_msgSend}} + // CALLS: {{call.*@objc_alloc}} + // CALLS: {{call.*@objc_allocWithZone}} + [NSObject alloc]; + [NSObject allocWithZone:nil]; +} + +// CHECK-LABEL: define {{.*}}void @test2 +void test2(void* x) { + // MSGS: {{call.*@objc_msgSend}} + // MSGS: {{call.*@objc_msgSend}} + // MSGS: {{call.*@objc_msgSend}} + // CALLS: {{call.*@objc_msgSend}} + // CALLS: {{call.*@objc_msgSend}} + // CALLS: {{call.*@objc_msgSend}} + [NSObject alloc2]; + [NSObject allocWithZone:(void*)-1]; + [NSObject allocWithZone:x]; +} + +@class A; +@interface B ++ (A*) alloc; ++ (A*)allocWithZone:(void*)zone; +@end + +// Make sure we get a bitcast on the return type as the +// call will return i8* which we have to cast to A* +// CHECK-LABEL: define {{.*}}void @test_alloc_class_ptr +A* test_alloc_class_ptr() { + // CALLS: {{call.*@objc_alloc}} + // CALLS-NEXT: bitcast i8* + // CALLS-NEXT: ret + return [B alloc]; +} + +// Make sure we get a bitcast on the return type as the +// call will return i8* which we have to cast to A* +// CHECK-LABEL: define {{.*}}void @test_alloc_class_ptr +A* test_allocWithZone_class_ptr() { + // CALLS: {{call.*@objc_allocWithZone}} + // CALLS-NEXT: bitcast i8* + // CALLS-NEXT: ret + return [B allocWithZone:nil]; +} + + +@interface C ++ (id)allocWithZone:(int)intArg; +@end + +// Make sure we only accept pointer types +// CHECK-LABEL: define {{.*}}void @test_allocWithZone_int +C* test_allocWithZone_int() { + // MSGS: {{call.*@objc_msgSend}} + // CALLS: {{call.*@objc_msgSend}} + return [C allocWithZone:3]; +} + diff --git a/test/Driver/objc-convert-messages-to-runtime-calls.m b/test/Driver/objc-convert-messages-to-runtime-calls.m new file mode 100644 index 0000000000..ed7be571eb --- /dev/null +++ b/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" -- 2.40.0