Skip to content

Commit

Permalink
Convert some ObjC retain/release msgSends to runtime calls.
Browse files Browse the repository at this point in the history
It is faster to directly call the ObjC runtime for methods such as retain/release instead of sending a message to those functions.

Differential Revision: https://reviews.llvm.org/D55869

Reviewed By: rjmccall

llvm-svn: 349952
  • Loading branch information
cooperp committed Dec 21, 2018
1 parent 6f53929 commit e5b64ea
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 0 deletions.
37 changes: 37 additions & 0 deletions clang/include/clang/Basic/ObjCRuntime.h
Expand Up @@ -173,6 +173,43 @@ class ObjCRuntime {
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
/// autorelease => objc_autorelease
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?
///
Expand Down
77 changes: 77 additions & 0 deletions clang/lib/CodeGen/CGObjC.cpp
Expand Up @@ -396,6 +396,29 @@ tryGenerateSpecializedMessageSend(CodeGenFunction &CGF, QualType ResultType,
}
break;

case OMF_autorelease:
if (ResultType->isObjCObjectPointerType() &&
CGM.getLangOpts().getGC() == LangOptions::NonGC &&
Runtime.shouldUseARCFunctionsForRetainRelease())
return CGF.EmitObjCAutorelease(Receiver, CGF.ConvertType(ResultType));
break;

case OMF_retain:
if (ResultType->isObjCObjectPointerType() &&
CGM.getLangOpts().getGC() == LangOptions::NonGC &&
Runtime.shouldUseARCFunctionsForRetainRelease())
return CGF.EmitObjCRetainNonBlock(Receiver, CGF.ConvertType(ResultType));
break;

case OMF_release:
if (ResultType->isVoidType() &&
CGM.getLangOpts().getGC() == LangOptions::NonGC &&
Runtime.shouldUseARCFunctionsForRetainRelease()) {
CGF.EmitObjCRelease(Receiver, ARCPreciseLifetime);
return nullptr;
}
break;

default:
break;
}
Expand Down Expand Up @@ -2006,6 +2029,11 @@ static llvm::Value *emitObjCValueOperation(CodeGenFunction &CGF,
llvm::FunctionType *fnType =
llvm::FunctionType::get(CGF.Int8PtrTy, CGF.Int8PtrTy, false);
fn = CGF.CGM.CreateRuntimeFunction(fnType, fnName);

// We have Native ARC, so set nonlazybind attribute for performance
if (llvm::Function *f = dyn_cast<llvm::Function>(fn))
if (fnName == "objc_retain")
f->addFnAttr(llvm::Attribute::NonLazyBind);
}

// Cast the argument to 'id'.
Expand Down Expand Up @@ -2510,6 +2538,55 @@ void CodeGenFunction::emitARCIntrinsicUse(CodeGenFunction &CGF, Address addr,
CGF.EmitARCIntrinsicUse(value);
}

/// Autorelease the given object.
/// call i8* \@objc_autorelease(i8* %value)
llvm::Value *CodeGenFunction::EmitObjCAutorelease(llvm::Value *value,
llvm::Type *returnType) {
return emitObjCValueOperation(*this, value, returnType,
CGM.getObjCEntrypoints().objc_autoreleaseRuntimeFunction,
"objc_autorelease");
}

/// Retain the given object, with normal retain semantics.
/// call i8* \@objc_retain(i8* %value)
llvm::Value *CodeGenFunction::EmitObjCRetainNonBlock(llvm::Value *value,
llvm::Type *returnType) {
return emitObjCValueOperation(*this, value, returnType,
CGM.getObjCEntrypoints().objc_retainRuntimeFunction,
"objc_retain");
}

/// Release the given object.
/// call void \@objc_release(i8* %value)
void CodeGenFunction::EmitObjCRelease(llvm::Value *value,
ARCPreciseLifetime_t precise) {
if (isa<llvm::ConstantPointerNull>(value)) return;

llvm::Constant *&fn = CGM.getObjCEntrypoints().objc_release;
if (!fn) {
if (!fn) {
llvm::FunctionType *fnType =
llvm::FunctionType::get(Builder.getVoidTy(), Int8PtrTy, false);
fn = CGM.CreateRuntimeFunction(fnType, "objc_release");
setARCRuntimeFunctionLinkage(CGM, fn);
// We have Native ARC, so set nonlazybind attribute for performance
if (llvm::Function *f = dyn_cast<llvm::Function>(fn))
f->addFnAttr(llvm::Attribute::NonLazyBind);
}
}

// Cast the argument to 'id'.
value = Builder.CreateBitCast(value, Int8PtrTy);

// Call objc_release.
llvm::CallInst *call = EmitNounwindRuntimeCall(fn, value);

if (precise == ARCImpreciseLifetime) {
call->setMetadata("clang.imprecise_release",
llvm::MDNode::get(Builder.getContext(), None));
}
}

namespace {
struct CallObjCAutoreleasePoolObject final : EHScopeStack::Cleanup {
llvm::Value *Token;
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Expand Up @@ -3798,6 +3798,11 @@ class CodeGenFunction : public CodeGenTypeCache {
llvm::Value *EmitARCRetainAutoreleasedReturnValue(llvm::Value *value);
llvm::Value *EmitARCUnsafeClaimAutoreleasedReturnValue(llvm::Value *value);

llvm::Value *EmitObjCAutorelease(llvm::Value *value, llvm::Type *returnType);
llvm::Value *EmitObjCRetainNonBlock(llvm::Value *value,
llvm::Type *returnType);
void EmitObjCRelease(llvm::Value *value, ARCPreciseLifetime_t precise);

std::pair<LValue,llvm::Value*>
EmitARCStoreAutoreleasing(const BinaryOperator *e);
std::pair<LValue,llvm::Value*>
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.h
Expand Up @@ -138,6 +138,10 @@ struct ObjCEntrypoints {
/// id objc_autorelease(id);
llvm::Constant *objc_autorelease;

/// id objc_autorelease(id);
/// Note this is the runtime method not the intrinsic.
llvm::Constant *objc_autoreleaseRuntimeFunction;

/// id objc_autoreleaseReturnValue(id);
llvm::Constant *objc_autoreleaseReturnValue;

Expand All @@ -162,6 +166,10 @@ struct ObjCEntrypoints {
/// id objc_retain(id);
llvm::Constant *objc_retain;

/// id objc_retain(id);
/// Note this is the runtime method not the intrinsic.
llvm::Constant *objc_retainRuntimeFunction;

/// id objc_retainAutorelease(id);
llvm::Constant *objc_retainAutorelease;

Expand All @@ -177,6 +185,10 @@ struct ObjCEntrypoints {
/// void objc_release(id);
llvm::Constant *objc_release;

/// void objc_release(id);
/// Note this is the runtime method not the intrinsic.
llvm::Constant *objc_releaseRuntimeFunction;

/// void objc_storeStrong(id*, id);
llvm::Constant *objc_storeStrong;

Expand Down
69 changes: 69 additions & 0 deletions clang/test/CodeGenObjC/convert-messages-to-runtime-calls.m
Expand Up @@ -14,16 +14,28 @@ @interface NSObject
+ (id)alloc;
+ (id)allocWithZone:(void*)zone;
+ (id)alloc2;
- (id)retain;
- (void)release;
- (id)autorelease;
@end

// CHECK-LABEL: define {{.*}}void @test1
void test1(id x) {
// MSGS: {{call.*@objc_msgSend}}
// MSGS: {{call.*@objc_msgSend}}
// MSGS: {{call.*@objc_msgSend}}
// MSGS: {{call.*@objc_msgSend}}
// MSGS: {{call.*@objc_msgSend}}
// CALLS: {{call.*@objc_alloc}}
// CALLS: {{call.*@objc_allocWithZone}}
// CALLS: {{call.*@objc_retain}}
// CALLS: {{call.*@objc_release}}
// CALLS: {{call.*@objc_autorelease}}
[NSObject alloc];
[NSObject allocWithZone:nil];
[x retain];
[x release];
[x autorelease];
}

// CHECK-LABEL: define {{.*}}void @test2
Expand All @@ -43,6 +55,8 @@ void test2(void* x) {
@interface B
+ (A*) alloc;
+ (A*)allocWithZone:(void*)zone;
- (A*) retain;
- (A*) autorelease;
@end

// Make sure we get a bitcast on the return type as the
Expand All @@ -65,9 +79,30 @@ + (A*)allocWithZone:(void*)zone;
return [B allocWithZone:nil];
}

// 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_retain_class_ptr
A* test_retain_class_ptr(B *b) {
// CALLS: {{call.*@objc_retain}}
// CALLS-NEXT: bitcast i8*
// CALLS-NEXT: ret
return [b retain];
}

// 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_autorelease_class_ptr
A* test_autorelease_class_ptr(B *b) {
// CALLS: {{call.*@objc_autorelease}}
// CALLS-NEXT: bitcast i8*
// CALLS-NEXT: ret
return [b autorelease];
}


@interface C
+ (id)allocWithZone:(int)intArg;
- (float) retain;
@end

// Make sure we only accept pointer types
Expand All @@ -78,3 +113,37 @@ + (id)allocWithZone:(int)intArg;
return [C allocWithZone:3];
}

// Make sure we use a message and not a call as the return type is
// not a pointer type.
// CHECK-LABEL: define {{.*}}void @test_cannot_message_return_float
float test_cannot_message_return_float(C *c) {
// MSGS: {{call.*@objc_msgSend}}
// CALLS: {{call.*@objc_msgSend}}
return [c retain];
}

@interface NSString : NSObject
+ (void)retain_self;
- (void)retain_super;
@end

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

@end

0 comments on commit e5b64ea

Please sign in to comment.