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

llvm-svn: 348687
This commit is contained in:
Pete Cooper 2018-12-08 05:13:50 +00:00
parent d076608d58
commit e388680dfa
10 changed files with 245 additions and 14 deletions

View File

@ -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()) {

View File

@ -1473,6 +1473,10 @@ def fno_zero_initialized_in_bss : Flag<["-"], "fno-zero-initialized-in-bss">, Gr
def fobjc_arc : Flag<["-"], "fobjc-arc">, Group<f_Group>, Flags<[CC1Option]>,
HelpText<"Synthesize retain and release calls for Objective-C pointers">;
def fno_objc_arc : Flag<["-"], "fno-objc-arc">, Group<f_Group>;
def fobjc_convert_messages_to_runtime_calls :
Flag<["-"], "fobjc-convert-messages-to-runtime-calls">, Group<f_Group>;
def fno_objc_convert_messages_to_runtime_calls :
Flag<["-"], "fno-objc-convert-messages-to-runtime-calls">, Group<f_Group>, Flags<[CC1Option]>;
def fobjc_arc_exceptions : Flag<["-"], "fobjc-arc-exceptions">, Group<f_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<f_Group>;

View File

@ -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.

View File

@ -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<llvm::Value *>
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<llvm::ConstantPointerNull>(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<llvm::Value *> 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) {

View File

@ -3805,6 +3805,10 @@ public:
std::pair<LValue,llvm::Value*>
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);

View File

@ -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);

View File

@ -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)

View File

@ -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;

View File

@ -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];
}

View File

@ -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"