title: Why the implementation of ARC's objc_autoreleaseReturnValue differs for x86_64 and ARM?
body: <p>After reading the excellent blog post by Mike Ash <a href="https://www.mikeash.com/pyblog/friday-qa-2014-05-09-when-an-autorelease-isnt.html">"Friday Q&amp;A 2014-05-09: When an Autorelease Isn't"</a> on ARC, I decided to check out the details of the optimisations that ARC applies to speed up the retain/release process.
  The trick I'm referring to is called "Fast autorelease" in which the caller and callee cooperate to keep the returned object out of the autorelease pool. This works best in situation like the following:</p>
  
  <pre><code>- (id) myMethod {
      id obj = [MYClass new];
      return [obj autorelease];
  }
  
  - (void) mainMethod {
     obj = [[self myMethod] retain];
     // Do something with obj
     [obj release];
  }
  </code></pre>
  
  <p>that can be optimised by skipping the autorelease pool completely:</p>
  
  <pre><code>- (id) myMethod {
      id obj = [MYClass new];
      return obj;
  }
  
  - (void) mainMethod {
     obj = [self myMethod];
     // Do something with obj
     [obj release];
  }
  </code></pre>
  
  <p>The way this optimisation is implemented is very interesting. I quote from Mike's post:</p>
  
  <blockquote>
    <p>"There is some extremely fancy and mind-bending code in the Objective-C runtime's implementation of autorelease. Before actually sending an autorelease message, it first inspects the caller's code. If it sees that the caller is going to immediately call objc_retainAutoreleasedReturnValue, it completely skips the message send. It doesn't actually do an autorelease at all. Instead, it just stashes the object in a known location, which signals that it hasn't sent autorelease at all."</p>
  </blockquote>
  
  <p>So far so good. The implementation for x86_64 on NSObject.mm is quite straightforward. The code analyses the assembler located after the return address of <code>objc_autoreleaseReturnValue</code> for the presence of a call to <code>objc_retainAutoreleasedReturnValue</code>.</p>
  
  <pre><code>static bool callerAcceptsFastAutorelease(const void * const ra0)
  {
      const uint8_t *ra1 = (const uint8_t *)ra0;
      const uint16_t *ra2;
      const uint32_t *ra4 = (const uint32_t *)ra1;
      const void **sym;
  
      //1. Navigate the DYLD stubs to get to the real pointer of the function to be called
      // 48 89 c7    movq  %rax,%rdi
      // e8          callq symbol
      if (*ra4 != 0xe8c78948) {
          return false;
      }
  
      ra1 += (long)*(const int32_t *)(ra1 + 4) + 8l;
      ra2 = (const uint16_t *)ra1;
      // ff 25       jmpq *symbol@DYLDMAGIC(%rip)
      if (*ra2 != 0x25ff) {
          return false;
      }
  
      ra1 += 6l + (long)*(const int32_t *)(ra1 + 2);
      sym = (const void **)ra1;
  
      //2. Check that the code to be called belongs to objc_retainAutoreleasedReturnValue
      if (*sym != objc_retainAutoreleasedReturnValue)
      {
          return false;
      }
  
      return true;
  }
  </code></pre>
  
  <p>But when it comes to ARM, I just can't understand how it works. The code looks like this (I've simplified a little bit):</p>
  
  <pre><code>static bool callerAcceptsFastAutorelease(const void *ra)
  {
      // 07 70 a0 e1    mov r7, r7
      if (*(uint32_t *)ra == 0xe1a07007) {
          return true;
      }
      return false;
  }
  </code></pre>
  
  <p>It looks like the code is identifying the presence of <code>objc_retainAutoreleasedReturnValue</code> not by looking up the presence of a call to that specific function, but by looking instead for a special no-op operation <code>mov r7, r7</code>. </p>
  
  <p>Diving into LLVM source code I found the following explanation:</p>
  
  <blockquote>
    <p>"The implementation of objc_autoreleaseReturnValue sniffs the instruction stream following its return address to decide whether it's a call to objc_retainAutoreleasedReturnValue. This can be prohibitively expensive, depending on the relocation model, and so on some targets it instead sniffs for a particular instruction sequence. This functions returns that instruction sequence in inline assembly, which will be empty if none is required."</p>
  </blockquote>
  
  <p>I was wondering why is that so on ARM? </p>
  
  <p>Having the compiler put there a certain marker so that a specific implementation of a library can find it sounds like a strong coupling between compiler and the library code. Why can't the "sniffing" be implemented the same way as on the x86_64 platform? </p>