iosobjective-cautomatic-ref-counting

What does objc_autoreleaseReturnValue mean?


I have a method createATestObject. As its name indicates, it create an object and return it. The code is very simple and it is under ARC.

- (TestObj *)createATestObj
{
    return [[TestObj alloc] init] ;
}

I assembly the file and get the assembly code below.

Lfunc_begin4:
    .cfi_startproc
@ BB#0:
    push    {r7, lr}
    mov r7, sp
    sub sp, #8
    @DEBUG_VALUE: -[ViewController createATestObj]:self <- undef
    @DEBUG_VALUE: -[ViewController createATestObj]:_cmd <- undef
    str r0, [sp, #4]
    str r1, [sp]
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC4_0+4))
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC4_0+4))
LPC4_0:
    add r0, pc
    ldr r0, [r0]
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_10-(LPC4_1+4))
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_10-(LPC4_1+4))
LPC4_1:
    add r1, pc
    ldr r1, [r1]
    blx _objc_msgSend
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC4_2+4))
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC4_2+4))
LPC4_2:
    add r1, pc
    ldr r1, [r1]
    blx _objc_msgSend
    add sp, #8
    pop.w   {r7, lr}
    b.w _objc_autoreleaseReturnValue
Ltmp13:
Lfunc_end4:
    .cfi_endproc

I noticed the function _objc_autoreleaseReturnValue and get the reference about it from here. But I didn't know what it means. Can anyone elaborate it ? Thanks very much.

id objc_autoreleaseReturnValue(id value);

Precondition: value is null or a pointer to a valid object.

If value is null, this call has no effect. Otherwise, it makes a best effort to hand off ownership of a retain count on the object to a call to objc_retainAutoreleasedReturnValue for the same object in an enclosing call frame. If this is not possible, the object is autoreleased as above.

Always returns value.


Solution

  • Thanks to @Darren first, thanks for your link.

    I reference some paragraph in the link.

    ARC has a trick that keeps returned objects out of autorelease pools if both the caller and callee are ARC.

    But how does that work? One of the features of ARC is that old compiled-before-ARC code (MRC code) can call ARC code and vice-versa. But if ARC code doesn’t put a returned object in an autorelease pool that MRC code is expecting, then the object would just leak.

    So ARC-ified clang code emits this function call when returning an object: objc_autoreleaseReturnValue(id).

    If you look at objc_autoreleaseReturnValue's implementation, it calls callerAcceptsFastAutorelease(). Even if you don’t read x86_64 or ARM assembly, the code’s comment is straight-forward:

    /*
      Fast handling of returned autoreleased values.
      The caller and callee cooperate to keep the returned object 
      out of the autorelease pool.
    
      Caller:
        ret = callee();
        objc_retainAutoreleasedReturnValue(ret);
        // use ret here
    
      Callee:
        // compute ret
        [ret retain];
        return objc_autoreleaseReturnValue(ret);
    
      objc_autoreleaseReturnValue() examines the caller's instructions following
      the return. If the caller's instructions immediately call
      objc_autoreleaseReturnValue, then the callee omits the -autorelease and saves
      the result in thread-local storage. If the caller does not look like it
      cooperates, then the callee calls -autorelease as usual.
    
      objc_autoreleaseReturnValue checks if the returned value is the same as the
      one in thread-local storage. If it is, the value is used directly. If not,
      the value is assumed to be truly autoreleased and is retained again.  In
      either case, the caller now has a retained reference to the value.
    
      Tagged pointer objects do participate in the fast autorelease scheme, 
      because it saves message sends. They are not entered in the autorelease 
      pool in the slow case.
    */
    

    Something From me

    Let's see the source code of objc_autoreleaseReturnValue.

    id 
    objc_autoreleaseReturnValue(id obj)
    {
    #if SUPPORT_RETURN_AUTORELEASE
        assert(tls_get_direct(AUTORELEASE_POOL_RECLAIM_KEY) == NULL);
    
        if (callerAcceptsFastAutorelease(__builtin_return_address(0))) {
            tls_set_direct(AUTORELEASE_POOL_RECLAIM_KEY, obj);
            return obj;
        }
    #endif
    
        return objc_autorelease(obj);
    }
    

    __builtin_return_address(0) returns the return address of current function, then let's look at the callerAcceptsFastAutorelease's implementation for arm version:

    static bool callerAcceptsFastAutorelease(const void *ra)
    {
        // if the low bit is set, we're returning to thumb mode
        if ((uintptr_t)ra & 1) {
            // 3f 46          mov r7, r7
            // we mask off the low bit via subtraction
            if (*(uint16_t *)((uint8_t *)ra - 1) == 0x463f) {
                return true;
            }
        } else {
            // 07 70 a0 e1    mov r7, r7
            if (*(uint32_t *)ra == 0xe1a07007) {
                return true;
            }
        }
        return false;
    }
    

    In the method, it looks for the instruction mov r7, r7 which is a marker for objc_retainAutoreleaseReturnValue, if it find that, then the method return true, so callee will omit the autorelease.

    You can see the assembly code of caller which is testFun1 in my case.

    - (void)testFun1:(ViewController *)vc
    {
        [vc createATestObj] ;
    }
    

    Below is the assembly code, you can find the line "mov r7, r7 @ marker for objc_retainAutoreleaseReturnValue"

        .cfi_startproc
    @ BB#0:
        push    {r7, lr}
        mov r7, sp
        sub sp, #16
        add r3, sp, #4
        movw    r9, #0
        movt    r9, #0
        str r0, [sp, #12]
        str r1, [sp, #8]
        str.w   r9, [sp, #4]
        mov r0, r3
        mov r1, r2
        bl  _objc_storeStrong
        movw    r0, :lower16:(L_objc_msgSend$non_lazy_ptr-(LPC5_0+4))
        movt    r0, :upper16:(L_objc_msgSend$non_lazy_ptr-(LPC5_0+4))
    LPC5_0:
        add r0, pc
        ldr r0, [r0]
        movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_12-(LPC5_1+4))
        movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_12-(LPC5_1+4))
    LPC5_1:
        add r1, pc
    Ltmp14:
        ldr r2, [sp, #4]
        ldr r1, [r1]
        str r0, [sp]                @ 4-byte Spill
        mov r0, r2
        ldr r2, [sp]                @ 4-byte Reload
        blx r2
        @ InlineAsm Start
        mov r7, r7      @ marker for objc_retainAutoreleaseReturnValue
        .code   16
        @ InlineAsm End
        bl  _objc_retainAutoreleasedReturnValue
        bl  _objc_release
        movs    r1, #0
        movt    r1, #0
        add r0, sp, #4
        bl  _objc_storeStrong
        add sp, #16
        pop {r7, pc}
    Ltmp15:
    Lfunc_end5:
        .cfi_endproc
    

    Update:

    I think there are some mistakes in comments of apple's implementation.

    The correct comment should be like this:

    /*
      Fast handling of returned autoreleased values.
      The caller and callee cooperate to keep the returned object 
      out of the autorelease pool.
    
      Caller:
        ret = callee();
        objc_retainAutoreleasedReturnValue(ret);
        // use ret here
    
      Callee:
        // compute ret
        [ret retain];
        return objc_autoreleaseReturnValue(ret);
    
      objc_autoreleaseReturnValue() examines the caller's instructions following
      the return. If the caller's instructions immediately call
      objc_retainAutoreleasedReturnValue, then the callee omits the -autorelease and saves
      the result in thread-local storage. If the caller does not look like it
      cooperates, then the callee calls -autorelease as usual.
    
      objc_retainAutoreleasedReturnValue checks if the returned value is the same as the
      one in thread-local storage. If it is, the value is used directly. If not,
      the value is assumed to be truly autoreleased and is retained again.  In
      either case, the caller now has a retained reference to the value.
    
      Tagged pointer objects do participate in the fast autorelease scheme, 
      because it saves message sends. They are not entered in the autorelease 
      pool in the slow case.
    */
    

    Leave a comment if you have other ideas.