how-arc-working

我们都知道ARC是苹果通过编译器在编译期支持的功能。但要问编译器对ARC是怎么支持的,很多人大概率说不出所以然来。

Objective-C的内存管理依靠引用计数,在对象被持有时增加计数,在对象被原持有对象释放的时候减少计数。在ARC之前,开发者是需要牢记retainrelease的。

我们来看一个示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface Hello : NSObject

@end

@implementation Hello

@end


int main() {
Hello *hello = [[Hello alloc] init];
return 0;
}

我们通过Clang前端来检查编译各阶段的情况。

clang -fobjc-arc -framework Foundation -fmodules -fsyntax-only -Xclang -ast-dump hello.m

输出AST:

在输出的AST中我们能找到与ARC相关的内容非常少,也没有看到插入objc_storeStrong等符号。唯一相关的是ARCConsumeObject。

clang -fobjc-arc -framework Foundation -S -emit-llvm hello.m
生成IR代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
define i32 @main() #0 {
%1 = alloca i32, align 4
%2 = alloca %0*, align 8
store i32 0, i32* %1, align 4
%3 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
%4 = bitcast %struct._class_t* %3 to i8*
%5 = call i8* @objc_alloc_init(i8* %4)
%6 = bitcast i8* %5 to %0*
store %0* %6, %0** %2, align 8
store i32 0, i32* %1, align 4
%7 = bitcast %0** %2 to i8**
call void @llvm.objc.storeStrong(i8** %7, i8* null) #1
%8 = load i32, i32* %1, align 4
ret i32 %8
}

clang -fobjc-arc -framework Foundation -S hello.ll -o hello.s
生成汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
	.section	__TEXT,__text,regular,pure_instructions
.build_version macos, 11, 0 sdk_version 12, 1
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
movq _OBJC_CLASSLIST_REFERENCES_$_(%rip), %rdi
callq _objc_alloc_init
xorl %ecx, %ecx
movl %ecx, %esi
movq %rax, -16(%rbp)
movl $0, -4(%rbp)
leaq -16(%rbp), %rdi
callq _objc_storeStrong
movl -4(%rbp), %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__objc_classname,cstring_literals
L_OBJC_CLASS_NAME_: ## @OBJC_CLASS_NAME_
.asciz "Hello"

.section __DATA,__objc_const
.p2align 3 ## @"_OBJC_METACLASS_RO_$_Hello"
__OBJC_METACLASS_RO_$_Hello:
.long 129 ## 0x81
.long 40 ## 0x28
.long 40 ## 0x28
.space 4
.quad 0
.quad L_OBJC_CLASS_NAME_
.quad 0
.quad 0
.quad 0
.quad 0
.quad 0

.section __DATA,__objc_data
.globl _OBJC_METACLASS_$_Hello ## @"OBJC_METACLASS_$_Hello"
.p2align 3
_OBJC_METACLASS_$_Hello:
.quad _OBJC_METACLASS_$_NSObject
.quad _OBJC_METACLASS_$_NSObject
.quad __objc_empty_cache
.quad 0
.quad __OBJC_METACLASS_RO_$_Hello

.section __DATA,__objc_const
.p2align 3 ## @"_OBJC_CLASS_RO_$_Hello"
__OBJC_CLASS_RO_$_Hello:
.long 128 ## 0x80
.long 8 ## 0x8
.long 8 ## 0x8
.space 4
.quad 0
.quad L_OBJC_CLASS_NAME_
.quad 0
.quad 0
.quad 0
.quad 0
.quad 0

.section __DATA,__objc_data
.globl _OBJC_CLASS_$_Hello ## @"OBJC_CLASS_$_Hello"
.p2align 3
_OBJC_CLASS_$_Hello:
.quad _OBJC_METACLASS_$_Hello
.quad _OBJC_CLASS_$_NSObject
.quad __objc_empty_cache
.quad 0
.quad __OBJC_CLASS_RO_$_Hello

.section __DATA,__objc_classrefs,regular,no_dead_strip
.p2align 3 ## @"OBJC_CLASSLIST_REFERENCES_$_"
_OBJC_CLASSLIST_REFERENCES_$_:
.quad _OBJC_CLASS_$_Hello

.section __DATA,__objc_classlist,regular,no_dead_strip
.p2align 3 ## @"OBJC_LABEL_CLASS_$"
l_OBJC_LABEL_CLASS_$:
.quad _OBJC_CLASS_$_Hello

.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64

.subsections_via_symbols

我们可以看到在AST阶段其实编译器并没有插入ARC相关代码,但在IR阶段便插入了`@llvm.objc.storeStrong(即汇编的_objc_storeStrong)。所以唯一的线索是ARCConsumeObject`。

顺着这条线索,我们查阅到了:

llvm-project/clang/lib/Sema/SemaExprObjC.cpp 中的Sema::CheckObjCConversion。

正是ARC处理之处。我直接把这部分代码贴在下面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
Sema::ARCConversionResult
Sema::CheckObjCConversion(SourceRange castRange, QualType castType,
                          Expr *&castExpr, CheckedConversionKind CCK,
                          bool Diagnose, bool DiagnoseCFAudited,
                          BinaryOperatorKind Opc) {
  QualType castExprType = castExpr->getType();

  // For the purposes of the classification, we assume reference types
  // will bind to temporaries.
  QualType effCastType = castType;
  if (const ReferenceType *ref = castType->getAs<ReferenceType>())
    effCastType = ref->getPointeeType();

  ARCConversionTypeClass exprACTC = classifyTypeForARCConversion(castExprType);
  ARCConversionTypeClass castACTC = classifyTypeForARCConversion(effCastType);
  if (exprACTC == castACTC) {
    // Check for viability and report error if casting an rvalue to a
    // life-time qualifier.
    if (castACTC == ACTC_retainable &&
        (CCK == CCK_CStyleCast || CCK == CCK_OtherCast) &&
        castType != castExprType) {
      const Type *DT = castType.getTypePtr();
      QualType QDT = castType;
      // We desugar some types but not others. We ignore those
      // that cannot happen in a cast; i.e. auto, and those which
      // should not be de-sugared; i.e typedef.
      if (const ParenType *PT = dyn_cast<ParenType>(DT))
        QDT = PT->desugar();
      else if (const TypeOfType *TP = dyn_cast<TypeOfType>(DT))
        QDT = TP->desugar();
      else if (const AttributedType *AT = dyn_cast<AttributedType>(DT))
        QDT = AT->desugar();
      if (QDT != castType &&
          QDT.getObjCLifetime() !=  Qualifiers::OCL_None) {
        if (Diagnose) {
          SourceLocation loc = (castRange.isValid() ? castRange.getBegin()
                                                    : castExpr->getExprLoc());
          Diag(loc, diag::err_arc_nolifetime_behavior);
        }
        return ACR_error;
      }
    }
    return ACR_okay;
  }

  // The life-time qualifier cast check above is all we need for ObjCWeak.
  // ObjCAutoRefCount has more restrictions on what is legal.
  if (!getLangOpts().ObjCAutoRefCount)
    return ACR_okay;

  if (isAnyCLike(exprACTC) && isAnyCLike(castACTC)) return ACR_okay;

  // Allow all of these types to be cast to integer types (but not
  // vice-versa).
  if (castACTC == ACTC_none && castType->isIntegralType(Context))
    return ACR_okay;

  // Allow casts between pointers to lifetime types (e.g., __strong id*)
  // and pointers to void (e.g., cv void *). Casting from void* to lifetime*
  // must be explicit.
  // Allow conversions between pointers to lifetime types and coreFoundation
  // pointers too, but only when the conversions are explicit.
  if (exprACTC == ACTC_indirectRetainable &&
      (castACTC == ACTC_voidPtr ||
       (castACTC == ACTC_coreFoundation && isCast(CCK))))
    return ACR_okay;
  if (castACTC == ACTC_indirectRetainable &&
      (exprACTC == ACTC_voidPtr || exprACTC == ACTC_coreFoundation) &&
      isCast(CCK))
    return ACR_okay;

  switch (ARCCastChecker(Context, exprACTC, castACTC, false).Visit(castExpr)) {
  // For invalid casts, fall through.
  case ACC_invalid:
    break;

  // Do nothing for both bottom and +0.
  case ACC_bottom:
  case ACC_plusZero:
    return ACR_okay;

  // If the result is +1, consume it here.
  case ACC_plusOne:
    castExpr = ImplicitCastExpr::Create(Context, castExpr->getType(),
                                        CK_ARCConsumeObject, castExpr, nullptr,
                                        VK_PRValue, FPOptionsOverride());
    Cleanup.setExprNeedsCleanups(true);
    return ACR_okay;
  }

  // If this is a non-implicit cast from id or block type to a
  // CoreFoundation type, delay complaining in case the cast is used
  // in an acceptable context.
  if (exprACTC == ACTC_retainable && isAnyRetainable(castACTC) && isCast(CCK))
    return ACR_unbridged;

  // Issue a diagnostic about a missing @-sign when implicit casting a cstring
  // to 'NSString *', instead of falling through to report a "bridge cast"
  // diagnostic.
  if (castACTC == ACTC_retainable && exprACTC == ACTC_none &&
      CheckConversionToObjCLiteral(castType, castExpr, Diagnose))
    return ACR_error;

  // Do not issue "bridge cast" diagnostic when implicit casting
  // a retainable object to a CF type parameter belonging to an audited
  // CF API function. Let caller issue a normal type mismatched diagnostic
  // instead.
  if ((!DiagnoseCFAudited || exprACTC != ACTC_retainable ||
       castACTC != ACTC_coreFoundation) &&
      !(exprACTC == ACTC_voidPtr && castACTC == ACTC_retainable &&
        (Opc == BO_NE || Opc == BO_EQ))) {
    if (Diagnose)
      diagnoseObjCARCConversion(*this, castRange, castType, castACTC, castExpr,
                                castExpr, exprACTC, CCK);
    return ACR_error;
  }
  return ACR_okay;
}

总结一句话就是说,ARC是编译器在AST之后、生成IR中间代码之前插入的代码。

Comments