Commit d18fbfc0 authored by Pierre Habouzit's avatar Pierre Habouzit
Browse files

Relax the rules around objc_alloc and objc_alloc_init optimizations.

Today the optimization is limited to:
- `[ClassName alloc]`
- `[self alloc]` when within a class method

However it means that when code is written this way:

```
    @interface MyObject
    - (id)copyWithZone:(NSZone *)zone
    {
        return [[self.class alloc] _initWith...];
    }

    @end
```

... then the optimization doesn't kick in and `+[NSObject alloc]` ends
up in IMP caches where it could have been avoided. It turns out that
`+alloc` -> `+[NSObject alloc]` is the most cached SEL/IMP pair in the
entire platform which is rather silly).

There's two theoretical risks allowing this optimization:

1. if the receiver is nil (which it can't be today), but it turns out
   that `objc_alloc()`/`objc_alloc_init()` cope with a nil receiver,

2. if the `Clas` type for the receiver is a lie. However, for such a
   code to work today (and not fail witn an unrecognized selector
   anyway) you'd have to have implemented the `-alloc` **instance
   method**.

   Fortunately, `objc_alloc()` doesn't assume that the receiver is a
   Class, it basically starts with a test that is similar to

       `if (receiver->isa->bits & hasDefaultAWZ) { /* fastpath */ }`.

   This bit is only set on metaclasses by the runtime, so if an instance
   is passed to this function by accident, its isa will fail this test,
   and `objc_alloc()` will gracefully fallback to `objc_msgSend()`.

   The one thing `objc_alloc()` doesn't support is tagged pointer
   instances. None of the tagged pointer classes implement an instance
   method called `'alloc'` (actually there's a single class in the
   entire Apple codebase that has such a method).

Differential Revision: https://reviews.llvm.org/D71682
Radar-Id: rdar://problem/58058316


Reviewed-By: Akira Hatanaka
Signed-off-by: default avatarPierre Habouzit <phabouzit@apple.com>
parent 0dbcb363
Loading
Loading
Loading
Loading
+19 −21
Original line number Diff line number Diff line
@@ -461,38 +461,39 @@ tryEmitSpecializedAllocInit(CodeGenFunction &CGF, const ObjCMessageExpr *OME) {
      Sel.getNameForSlot(0) != "init")
    return None;

  // Okay, this is '[receiver init]', check if 'receiver' is '[cls alloc]' or
  // we are in an ObjC class method and 'receiver' is '[self alloc]'.
  // Okay, this is '[receiver init]', check if 'receiver' is '[cls alloc]'
  // with 'cls' a Class.
  auto *SubOME =
      dyn_cast<ObjCMessageExpr>(OME->getInstanceReceiver()->IgnoreParenCasts());
  if (!SubOME)
    return None;
  Selector SubSel = SubOME->getSelector();

  // Check if we are in an ObjC class method and the receiver expression is
  // 'self'.
  const Expr *SelfInClassMethod = nullptr;
  if (const auto *CurMD = dyn_cast_or_null<ObjCMethodDecl>(CGF.CurFuncDecl))
    if (CurMD->isClassMethod())
      if ((SelfInClassMethod = SubOME->getInstanceReceiver()))
        if (!SelfInClassMethod->isObjCSelfExpr())
          SelfInClassMethod = nullptr;

  if ((SubOME->getReceiverKind() != ObjCMessageExpr::Class &&
       !SelfInClassMethod) || !SubOME->getType()->isObjCObjectPointerType() ||
  if (!SubOME->getType()->isObjCObjectPointerType() ||
      !SubSel.isUnarySelector() || SubSel.getNameForSlot(0) != "alloc")
    return None;

  llvm::Value *Receiver;
  if (SelfInClassMethod) {
    Receiver = CGF.EmitScalarExpr(SelfInClassMethod);
  } else {
  llvm::Value *Receiver = nullptr;
  switch (SubOME->getReceiverKind()) {
  case ObjCMessageExpr::Instance:
    if (!SubOME->getInstanceReceiver()->getType()->isObjCClassType())
      return None;
    Receiver = CGF.EmitScalarExpr(SubOME->getInstanceReceiver());
    break;

  case ObjCMessageExpr::Class: {
    QualType ReceiverType = SubOME->getClassReceiver();
    const ObjCObjectType *ObjTy = ReceiverType->castAs<ObjCObjectType>();
    const ObjCInterfaceDecl *ID = ObjTy->getInterface();
    assert(ID && "null interface should be impossible here");
    Receiver = CGF.CGM.getObjCRuntime().GetClass(CGF, ID);
    break;
  }
  case ObjCMessageExpr::SuperInstance:
  case ObjCMessageExpr::SuperClass:
    return None;
  }

  return CGF.EmitObjCAllocInit(Receiver, CGF.ConvertType(OME->getType()));
}

@@ -540,10 +541,7 @@ RValue CodeGenFunction::EmitObjCMessageExpr(const ObjCMessageExpr *E,
  switch (E->getReceiverKind()) {
  case ObjCMessageExpr::Instance:
    ReceiverType = E->getInstanceReceiver()->getType();
    if (auto *OMD = dyn_cast_or_null<ObjCMethodDecl>(CurFuncDecl))
      if (OMD->isClassMethod())
        if (E->getInstanceReceiver()->isObjCSelfExpr())
          isClassMessage = true;
    isClassMessage = ReceiverType->isObjCClassType();
    if (retainSelf) {
      TryEmitResult ter = tryEmitARCRetainScalarExpr(*this,
                                                   E->getInstanceReceiver());
+10 −1
Original line number Diff line number Diff line
@@ -22,21 +22,30 @@ void f() {
}

@interface Y : X
+(Class)class;
+(void)meth;
-(void)instanceMeth;
@end

@implementation Y
+(Class)class {
  return self;
}
+(void)meth {
  [[self alloc] init];
  // OPTIMIZED: call i8* @objc_alloc_init(
  // NOT_OPTIMIZED: call i8* @objc_alloc(
}
+ (void)meth2 {
  [[[self class] alloc] init];
  // OPTIMIZED: call i8* @objc_alloc_init(
  // NOT_OPTIMIZED: call i8* @objc_alloc(
}
-(void)instanceMeth {
  // EITHER-NOT: call i8* @objc_alloc
  // EITHER: call {{.*}} @objc_msgSend
  // EITHER: call {{.*}} @objc_msgSend
  [[self alloc] init];
  [[(id)self alloc] init];
}
@end