Czy kompilator może stale składać zmienną lokalną?

25

Rozważ ten prosty kod:

void g();

void foo()
{
    volatile bool x = false;
    if (x)
        g();
}

https://godbolt.org/z/I2kBY7

Widać, że ani gccnie clangoptymalizuje potencjalnego połączenia z g. W moim rozumieniu jest to poprawne: maszyna abstrakcyjna ma zakładać, że volatilezmienne mogą się zmieniać w dowolnym momencie (z powodu np. Mapowania sprzętowego), więc ciągłe składanie falseinicjalizacji do ifsprawdzenia byłoby błędne.

Ale MSVC gcałkowicie eliminuje wezwanie do (zachowując odczyty i zapisy do volatiletego!). Czy to zachowanie jest zgodne ze standardami?


Tło: czasami używam tego rodzaju konstrukcji, aby móc włączać / wyłączać wyjście debugowania w locie: kompilator musi zawsze odczytywać wartość z pamięci, więc zmiana tej zmiennej / pamięci podczas debugowania powinna odpowiednio zmodyfikować przepływ sterowania . Wyjście MSVC ponownie odczytuje wartość, ale ignoruje ją (prawdopodobnie ze względu na ciągłe składanie i / lub eliminację martwego kodu), co oczywiście nie zgadza się z moimi intencjami.


Edycje:

  • Eliminacja odczytów i zapisów volatilejest omawiana tutaj: Czy kompilator może zoptymalizować lokalną zmienną zmienną? (dzięki Nathan!). Myślę, że standard jest całkowicie jasny, że te odczyty i zapisy muszą się zdarzyć. Ale ta dyskusja nie obejmuje tego, czy kompilator może przyjmować wyniki tych odczytów za pewnik i optymalizować na ich podstawie. Przypuszczam, że jest to niedokreślone / nieokreślone w standardzie, ale byłbym szczęśliwy, gdyby ktoś udowodnił, że się mylę.

  • Mogę oczywiście utworzyć xzmienną nielokalną, aby rozwiązać problem. To pytanie jest bardziej z ciekawości.

Max Langhof
źródło
3
Dla mnie wygląda to na oczywisty błąd kompilatora.
Sam Varshavchik
1
O ile mi wiadomo, jest to legalne na zasadzie jakby. Kompilator może udowodnić, że chociaż obiekt jest lotny, nie ma możliwości zmodyfikowania jego stanu, aby można go było rozłożyć. Nie jestem wystarczająco pewny siebie, aby odpowiedzieć na to pytanie, ale uważam, że jest to poprawne.
NathanOliver
3
Myślę jednak, że argument OP, że zmienna może być modyfikowana przez debugger, jest uzasadniony. Może ktoś powinien zgłosić błąd w MSVC.
Brian
2
@curiousguy Nawet jeśli odrzucisz wynik i / lub przyjmiesz dokładną wartość, nadal ją przeczytałeś.
Deduplicator,
2
Co ciekawe, robi to tylko dla x64. Wersja x86 nadal wywołuje g () godbolt.org/z/nc3Y-f
Jerry Jeremiah

Odpowiedzi:

2

Myślę, że [intro. Wykonanie] (numer paragrafu może się różnić) może być użyte do wyjaśnienia zachowania MSVC:

Wystąpienie każdego obiektu z automatycznym czasem przechowywania jest powiązane z każdym wejściem do jego bloku. Taki obiekt istnieje i zachowuje ostatnią zapamiętaną wartość podczas wykonywania bloku i gdy blok jest zawieszony ...

Norma nie pozwala na wyeliminowanie odczytu przez lotną wartość glvalue, ale powyższy akapit można interpretować jako pozwalający przewidzieć wartość false.


BTW, mówi C Standard (N1570 6.2.4 / 2)

Obiekt istnieje, ma stały adres i zachowuje ostatnią zapamiętaną wartość przez cały okres użytkowania. 34


34) W przypadku obiektu lotnego ostatni sklep nie musi być jawny w programie.

Nie jest jasne, czy mógłby istnieć niepisany zapis w obiekcie o automatycznym czasie przechowywania w pamięci C / modelu obiektowym.

Prawnik językowy
źródło
Zgadzam się, że kompilator może wiedzieć, kiedy możliwe są sklepy
MM
1
Więc jeśli to prawda, to lokalne obiekty lotne są (przynajmniej na MSVC) całkowicie bezcelowe? Czy jest coś, co dodaje volatilekupowanie (oprócz zbędnych odczytów / zapisów), jeśli jest ignorowane w celu optymalizacji?
Max Langhof,
1
@MaxLanghof Istnieje różnica między całkowicie bezcelowym a niezrealizowaniem oczekiwanego / oczekiwanego efektu.
Deduplicator
1
@MaxLanghof Wyniki pośrednie obliczeń zmiennoprzecinkowych są czasami promowane do 80-bitowego rejestru powodującego problemy z precyzją. Uważam, że jest to gcc-ism i można go uniknąć, ogłaszając wszystkie takie podwójne jako zmienne. Zobacz: gcc.gnu.org/bugzilla/show_bug.cgi?id=323
ForeverLearning
1
@philipxy PS Zobacz moją odpowiedź Wiem, że dostęp jest zdefiniowany w implementacji. Pytanie nie dotyczy dostępu (dostęp do obiektu), ale przewidywania wartości.
Language Lawyer
2

TL; DR Kompilator może robić, co chce, przy każdym dostępie lotnym. Ale dokumentacja musi ci powiedzieć. - „Semantyka dostępu przez niestabilną glvalue jest zdefiniowana w implementacji”.


Norma określa dla programu dozwolone sekwencje „lotnych dostępów” i innych „obserwowalnych zachowań” (osiąganych poprzez „skutki uboczne”), które wdrożenie musi przestrzegać zgodnie z „zasadą„ jak gdyby ”.

Ale standard mówi (moje pogrubienie):

Roboczy projekt, standard dla języka programowania C ++
Numer dokumentu: N4659
Data: 2017-03-21

§ 10.1.7.1 Kwalifikatory cv

5 Semantyka dostępu poprzez niestabilną glvalue jest zdefiniowana w implementacji. […]

Podobnie w przypadku urządzeń interaktywnych (moje pogrubienie):

§ 4.6 Realizacja programu

5 Zgodna implementacja wykonująca poprawnie sformułowany program powinna dawać takie same obserwowalne zachowanie jak jedno z możliwych wykonań odpowiedniej instancji abstrakcyjnej maszyny z tym samym programem i tym samym wejściem. [...]

7 Najmniejsze wymagania dotyczące zgodnego wdrożenia to:

(7.1) - Dostępy przez lotne wartości glvalu są oceniane ściśle zgodnie z regułami maszyny abstrakcyjnej.
(7.2) - Po zakończeniu programu wszystkie dane zapisane w plikach powinny być identyczne z jednym z możliwych wyników, jakie wywołałoby wykonanie programu zgodnie z abstrakcyjną semantyką.
(7.3) - Dynamika wejściowa i wyjściowa urządzeń interaktywnych powinna odbywać się w taki sposób, że wywołanie wyjściowe jest faktycznie dostarczane, zanim program zacznie czekać na dane wejściowe. To, co stanowi urządzenie interaktywne, jest zdefiniowane w implementacji.

Te łącznie określa się jako obserwowalne zachowanie programu. [...]

(W każdym razie, jaki konkretny kod jest generowany dla programu, nie jest określony przez standard).

Więc chociaż standard mówi, że niestabilnych dostępów nie można uciec od abstrakcyjnych sekwencji abstrakcyjnych efektów ubocznych maszyny i konsekwencji możliwych do zaobserwowania zachowań, które definiuje jakiś kod (być może), nie można oczekiwać, że cokolwiek zostanie odzwierciedlone w kodzie obiektowym lub w świecie rzeczywistym zachowanie, chyba że dokumentacja kompilatora mówi ci, co stanowi niestabilny dostęp . To samo dotyczy urządzeń interaktywnych.

Jeśli jesteś zainteresowany lotny vis a vis abstrakcyjnych sekwencji abstrakcyjnych efektów ubocznych maszyna i / lub wynikają obserwowalnych zachowań, które jakiś kod (może) definiuje wtedy tak powiedzieć . Ale jeśli jesteś zainteresowany tym, jaki generowany jest odpowiedni kod obiektowy , musisz interpretować to w kontekście twojego kompilatora i kompilacji .

Chronicznie ludzie błędnie uważają, że w przypadku niestabilnego dostępu ocena abstrakcyjna maszyny / odczyt powoduje zaimplementowany odczyt, a abstrakcyjne przypisanie / zapis maszyny powoduje zaimplementowany zapis. Nie ma podstaw, by twierdzić, że nie ma takiej dokumentacji dotyczącej wdrożenia. Kiedy / iff implementacja mówi, że faktycznie robi coś po „niestabilnym dostępie”, ludzie mają uzasadnione oczekiwania, że ​​coś - być może, wygenerowanie określonego kodu obiektowego.

philipxy
źródło
1
Mam na myśli, że sprowadza się to do tego, że „jeśli wymyślę maszynę, w której wszystkie wspomniane efekty uboczne nie występują, to mam legalną implementację C ++, kompilując każdy program do braku operacji” . Oczywiście interesują nas praktycznie obserwowalne efekty, ponieważ abstrakcyjne efekty uboczne maszyn są abstrakcyjnie tautologicznie. Wymaga to pewnej podstawowej koncepcji skutków ubocznych i pomyślałbym, że „niestabilne dostępy prowadzą do jawnych instrukcji dostępu do pamięci” jest częścią tego (nawet jeśli standard nie ma znaczenia), więc tak naprawdę nie kupuję „powiedzmy” jeśli chcesz kod zamiast semantyki abstrakcyjnej ". Nadal +1.
Max Langhof
Tak, jakość wdrożenia jest istotna. Niemniej jednak, oprócz zapisu do plików, „obserwowalne” [sic] są zdefiniowane w implementacji. Jeśli chcesz mieć możliwość ustawiania punktów przerwania, uzyskiwania dostępu do konkretnej pamięci, ignorowania „ulotności” itp. W abstrakcyjnych ulotnych odczytach i zapisach, musisz poprosić program kompilatora o wyprowadzenie odpowiedniego kodu . PS W C ++ „efekt uboczny” nie jest stosowany do zachowania obserwacyjnego jako taki, służy do opisania częściowej oceny podwyrażeń.
philipxy
Chcesz wyjaśnić [sic]? Które źródło cytujesz i jaki jest błąd?
Max Langhof,
Mówiąc konkretnie, mam na myśli, że abstrakcyjna obserwowalna maszyna jest możliwa do zaobserwowania tylko w rzeczywistym świecie, jeśli i jak implementacja mówi, że jest.
philipxy
1
Czy twierdzisz, że implementacja może twierdzić, że nie ma czegoś takiego jak urządzenie interaktywne, więc każdy program może zrobić wszystko i nadal byłby poprawny? (Uwaga: nie rozumiem twojego nacisku na urządzenia interaktywne).
ciekawy
-1

Uważam, że pomijanie czeku jest legalne.

Akapit, który każdy lubi cytować

34) W przypadku obiektu lotnego ostatni sklep nie musi być jawny w programie

nie oznacza, że ​​implementacja musi zakładać, że takie sklepy są możliwe w dowolnym momencie lub dla dowolnej zmiennej lotnej. Wdrożenie wie, które sklepy są możliwe. Na przykład całkowicie uzasadnione jest założenie, że takie niejawne zapisy zdarzają się tylko w przypadku zmiennych lotnych, które są mapowane do rejestrów urządzeń, i że takie mapowanie jest możliwe tylko w przypadku zmiennych z zewnętrznym sprzężeniem. Lub implementacja może zakładać, że takie zapisy zdarzają się tylko w lokalizacjach pamięci o dopasowanych słowach.

Mimo to myślę, że zachowanie MSVC jest błędem. Nie ma żadnego rzeczywistego powodu, aby zoptymalizować połączenie. Taka optymalizacja może być zgodna, ale jest niepotrzebnie zła.

n. „zaimki” m.
źródło
Czy możesz wyjaśnić, dlaczego jest zły? W pokazach kodu funkcja nie może być dosłownie nigdy wywołana.
David Schwartz
@DavidSchwartz Można jedynie stwierdzić, że po określeniu semantyki zmiennych lokalnych (patrz cytowany akapit powyżej). Sam standard zauważa, że volatilema to być wskazówka dla implementacji, że wartość może się zmienić w sposób nieznany implementacji.
Max Langhof
@MaxLanghof W żaden sposób implementacja nie może poprawnie obsłużyć czegoś nieznanego. Pożyteczne platformy faktycznie określają, co możesz, a czego nie możesz używać volatilena tej platformie, a poza specyfikacją zawsze będzie to strzelanie do bzdur.
David Schwartz
@DavidSchwartz Oczywiście, że może - postępując zgodnie z semantyką (w szczególności odczytów i zapisów) maszyny abstrakcyjnej. Może nie być w stanie poprawnie zoptymalizować - to jest punkt standardu. Teraz jest to notatka, a zatem nie normatywna, a jak powiedzieliśmy obaj, implementacja może określić, co to volatilerobi i trzymać się tego. Chodzi mi o to, że sam kod (zgodnie ze standardową / abstrakcyjną maszyną C ++) nie pozwala ci ustalić, czy gmożna go wywołać.
Max Langhof
@DavidSchwartz Kod nic takiego nie pokazuje, ponieważ brak niejawnych zapisów nie wynika z kodu.
n. „zaimki” m.