Почему компилятор Rust не оптимизирует код, предполагая, что две изменяемые ссылки не могут иметь псевдоним?

Насколько я знаю, псевдонимы ссылок и указателей могут препятствовать способности компилятора генерировать оптимизированный код, поскольку они должны обеспечивать правильное поведение сгенерированного двоичного файла в случае, когда две ссылки/указатели действительно являются псевдонимами. Например, в следующем C-коде

void adds(int  *a, int *b) {
    *a += *b;
    *a += *b;
}

при компиляции с помощью clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final) с флагом -O3 он излучает

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)  # The first time
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)  # The second time
   a:    c3                       retq

Здесь код сохраняется дважды в (%rdi) в случае псевдонимов int *a и int *b.

Когда мы явно сообщаем компилятору, что эти два указателя не могут иметь псевдоним с ключевым словом restrict:

void adds(int * restrict a, int * restrict b) {
    *a += *b;
    *a += *b;
}

Тогда Clang создаст более оптимизированную версию двоичного кода:

0000000000000000 <adds>:
   0:    8b 06                    mov    (%rsi),%eax
   2:    01 c0                    add    %eax,%eax
   4:    01 07                    add    %eax,(%rdi)
   6:    c3                       retq

Поскольку Rust гарантирует (за исключением небезопасного кода), что две изменяемые ссылки не могут иметь псевдоним, я думаю, что компилятор должен иметь возможность создавать более оптимизированную версию кода.

Когда я тестирую с помощью приведенного ниже кода и компилирую его с rustc 1.35.0 с помощью -C opt-level=3 --emit obj,

#![crate_type = "staticlib"]
#[no_mangle]
fn adds(a: &mut i32, b: &mut i32) {
    *a += *b;
    *a += *b;
}

он генерирует:

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)
   a:    c3                       retq

Это не дает гарантии, что a и b не могут использовать псевдоним.

Это потому, что текущий компилятор Rust все еще находится в разработке и еще не включил анализ псевдонимов для оптимизации?

Это потому, что все еще существует вероятность того, что a и b могут использовать псевдоним даже в безопасном Rust?

Ответ 1

Первоначально Rust включил атрибут LLVM noalias, но это вызвало неправильный код. Когда все поддерживаемые версии LLVM больше не будут неправильно компилировать код, он будет снова включен.

Если вы добавите -Zmutable-noalias=yes в опции компилятора, вы получите ожидаемую сборку:

adds:
        mov     eax, dword ptr [rsi]
        add     eax, eax
        add     dword ptr [rdi], eax
        ret

Проще говоря, Rust повсеместно ставит эквивалент ключевого слова C restrict everywhere, гораздо более распространенный, чем любая обычная программа на C. Это использовало angular случаи LLVM больше, чем он мог правильно обработать. Оказывается, что программисты на C и C++ просто не используют restrict так часто, как &mut используется в Rust.

Это происходило несколько раз.

  • Руст с 1.0 до 1.7 - noalias включен
  • Руст от 1,8 до 1,27 - noalias отключен
  • Руст с 1.28 до 1.29 - noalias включен
  • Руст 1.30 через??? - noalias отключен

Связанные с ржавчиной проблемы