Почему LLVM ARC повторно сохраняет объекты перед их выбросом?

#objective-c #exception #automatic-ref-counting

#objective-c #исключение #автоматический подсчет ссылок

Вопрос:

Рассмотрим следующие простые функции:

 void simple_throw(id object) {
    @throw object;
}

void extra_throw(id object) {
    id tmp0 = object;
    id tmp1 = tmp0;
    id tmp2 = tmp1;
    @throw tmp2;
}
  

Изначально я думал, что обе эти функции должны компилироваться в одну сборку, и обе должны быть эквивалентны простому вызову objc_exception_throw(object) (поскольку аргументы функции по умолчанию autorelease находятся в ARC), возможно, с одним objc_retain(object) добавлением (поскольку ARC не является безопасным для исключений и по умолчанию имеет утечку).

Но это не так. Вот сборка (удаление некоторых дополнительных пух и .cfi_* директив; аргументы clang: -fobjc-arc -Ofast -fomit-frame-pointer -S ):

 _simple_throw:
    pushq   %rax
    callq   *_objc_retain@GOTPCREL(%rip)
    movq    %rax, %rdi
    callq   _objc_retainAutorelease
    movq    %rax, %rdi
    callq   _objc_exception_throw

_extra_throw:
    pushq   %rbx
    movq    _objc_retain@GOTPCREL(%rip), %rbx
    callq   *%rbx
    movq    %rax, %rdi
    callq   *%rbx
    movq    %rax, %rdi
    callq   *%rbx
    movq    %rax, %rdi
    callq   *%rbx
    movq    %rax, %rdi
    callq   _objc_retainAutorelease
    movq    %rax, %rdi
    callq   _objc_exception_throw
  

simple_throw сохраняет object дважды и extra_throw сохраняет object 5 раз!

Это приводит к двум вопросам:

  1. Какова оптимальная сборка (с точки зрения сохранения и автоматического освобождения), для simple_throw которой компилятор должен генерировать (предполагая, что мы хотим, чтобы сгенерированный код был совместим с ARC)?
  2. Почему присвоения локальным переменным в extra_throw приводят к дополнительным сохранениям? Я склонен думать, что это ошибка компилятора.

Комментарии:

1. Как бы то ни было, он делает то же самое, если вы заменяете @throw операторы вызовами noreturn функции. Кажется, что ошибка.

2. Я думаю, что обычно он добавляет соответствующие вызовы release в конце области видимости локальных переменных / параметров, а проход оптимизации удаляет избыточные пары. Операторы no-return препятствуют достижению «естественного» конца области видимости, поэтому они просто отбрасываются, и сохранение не может быть оптимизировано.

3. Это кажется очень правдоподобным!