Perché il compilatore Rust non ottimizza il codice supponendo che due riferimenti mutabili non possano essere alias?


301

Per quanto ne so, l'aliasing di riferimento / puntatore può ostacolare la capacità del compilatore di generare codice ottimizzato, dal momento che devono assicurarsi che il binario generato si comporti correttamente nel caso in cui i due riferimenti / puntatori siano effettivamente alias. Ad esempio, nel seguente codice C,

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

quando compilato da clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)con la -O3bandiera, emette

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

Qui il codice viene archiviato (%rdi)due volte nel caso int *ae int *balias.

Quando diciamo esplicitamente al compilatore che questi due puntatori non possono alias con la restrictparola chiave:

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

Quindi Clang emetterà una versione più ottimizzata del codice binario:

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

Dal momento che Rust si assicura (tranne che nel codice non sicuro) che due riferimenti mutabili non possono essere alias, penso che il compilatore dovrebbe essere in grado di emettere la versione più ottimizzata del codice.

Quando eseguo il test con il codice seguente e lo compilo con rustc 1.35.0con -C opt-level=3 --emit obj,

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

genera:

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

Questo non sfrutta la garanzia che ae bnon può alias.

Questo perché l'attuale compilatore Rust è ancora in fase di sviluppo e non ha ancora incorporato l'analisi degli alias per eseguire l'ottimizzazione?

È perché c'è ancora una possibilità che ae balias, anche in Rust sicuro?



76
Nota laterale: " Poiché Rust si assicura (tranne che nel codice non sicuro) che due riferimenti mutabili non possono alias " - vale la pena ricordare che anche nel unsafecodice, i riferimenti mutabili alias non sono consentiti e comportano un comportamento indefinito. È possibile avere alias di puntatori unsafenon elaborati, ma il codice non consente in realtà di ignorare le regole standard di Rust. È solo un malinteso comune e quindi vale la pena sottolineare.
Lukas Kalbertodt,

6
Mi ci è voluto un po 'di tempo per capire a cosa stia dando l'esempio, perché non sono bravo a leggere l'asm, quindi nel caso in cui aiuti qualcun altro: si riduce a se le due +=operazioni nel corpo di addspossano essere reinterpretate come *a = *a + *b + *b. Se i puntatori non lo fanno alias, si può, si può anche vedere ciò che equivale a b* + *bnel secondo asm messa in vendita: 2: 01 c0 add %eax,%eax. Ma se fanno alias, non possono, perché quando aggiungi *bper la seconda volta, conterrà un valore diverso rispetto alla prima volta (quello che memorizzi sulla riga 4:del primo elenco asm).
Dlukes

Risposte:


364

Ruggine originariamente faceva consentire di LLVM noaliasattributo, ma questo codice miscompiled causato . Quando tutte le versioni LLVM supportate non comprenderanno più correttamente il codice, verrà riattivato .

Se aggiungi -Zmutable-noalias=yes alle opzioni del compilatore, si ottiene l'assembly previsto:

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

In poche parole, Rust ha messo l'equivalente della restrictparola chiave di C. ovunque , molto più diffusa di qualsiasi normale programma C. Ciò ha esercitato casi angolari di LLVM più di quanto fosse in grado di gestire correttamente. Si scopre che i programmatori C e C ++ semplicemente non usano restrictcosì frequentemente come &mutin Rust.

È successo più volte .

  • Rust da 1.0 a 1.7 - noaliasabilitato
  • Ruggine da 1,8 a 1,27 - noalias disabilitato
  • Ruggine da 1.28 a 1.29 - noalias abilitato
  • Ruggine da 1,30 a ??? -noalias disabilitato

Problemi di ruggine correlati


12
Questo non è sorprendente. Nonostante le sue affermazioni a largo raggio di compatibilità con più lingue, LLVM è stato specificamente progettato come backend C ++ e ha sempre avuto una forte tendenza a soffocare su cose che non sembrano abbastanza C ++.
Mason Wheeler

47
@MasonWheeler se si fa clic attraverso alcuni dei problemi, si possono trovare esempi di codice C che l'uso restricte miscompile sia Clang e GCC. Non è limitato ai linguaggi che non sono "abbastanza C ++", a meno che non contiate C ++ stesso in quel gruppo .
Shepmaster,

6
@MasonWheeler: Non penso che LLVM sia stato progettato in base alle regole di C o C ++, ma piuttosto attorno alle regole di LLVM. Fa ipotesi che di solito valgono per il codice C o C ++, ma da quello che posso dire il progetto si basa su un modello di dipendenze di dati statici che non è in grado di gestire casi angolari difficili. Ciò andrebbe bene se presupponesse pessimisticamente dipendenze dei dati che non possono essere smentite, ma invece tratterebbe come azioni non operative che scriverebbero lo storage con lo stesso modello di bit in cui si trovava e che hanno dipendenze di dati potenziali ma non dimostrabili sul leggi e scrivi.
supercat

8
@supercat Ho letto i tuoi commenti alcune volte, ma ammetto di essere perplesso - non ho idea di cosa abbiano a che fare con questa domanda o risposta. Qui non entra in gioco un comportamento indefinito, questo è "solo" un caso di più passaggi di ottimizzazione che interagiscono male l'uno con l'altro.
Shepmaster,

2
@avl_sweden per ribadire, è solo un bug . Il passaggio dell'ottimizzazione di srotolamento del ciclo non ha (completamente?) noaliasTenuto conto dei puntatori durante l'esecuzione. Ha creato nuovi puntatori basati su puntatori di input, copiando erroneamente l' noaliasattributo anche se i nuovi puntatori hanno fatto un alias.
Shepmaster
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.