Koszmar kompatybilności: dwadzieścia lat od premiery GCC 2.96
Mija właśnie 20 lat od wydania jednej z najważniejszych wersji kompilatora GCC w historii. Wydanie 2.96, bo to o nim mowa, przysporzyło wieloletnich problemów ze zgodnością i dziś uchodzi za jeden z koronnych przykładów na to, jak nie należy rozwiązywać problemów w inżynierii oprogramowania. Problem z wersją 2.96 polega bowiem na tym, że taka wersja nie istnieje: "GCC 2.96" to nazwa pakietu stworzonego przez firmę Red Hat w 2000 roku. Jest to niestabilna wersja rozwojowa 3.0, połatana na szybko ze względu na naglące terminy. Szóstego października 2000, projekt GNU oficjalnie odciął się od tego wynalazku.
05.10.2020 | aktual.: 05.10.2020 01:17
W jaki sposób wersja kompilatora sprzed dwóch dekad ma dziś jakiekolwiek znaczenie? Odpowiedzi są dwie. Po pierwsze, nauczka. Powody, dla których Red Hat przygotował ten pakiet są dziś uznawane na przeciwieństwo dobrych praktyk (przyjrzymy się im za chwilę). Po drugie, miało to miejsce w czasach, gdy o wiele częściej decydowano się na "standaryzowanie" na konkretnej wersji. Jeżeli jakieś API okazało się ślepą uliczką, w przypadku większych projektów może się to wiązać z dożywotnim budowaniem kodu w dziwaczny sposób.
Katedra vs bazar
Kompilator GCC w okolicach roku 2000 znajdował się na rozdrożu. Projekt rozwijał się w żółwim tempie, podlegał ścisłej kontroli i centralizacji w wykonaniu FSF, był mało innowacyjny i prześcigały go sforkowane z niego, mniejsze projekty. FSF w końcu poddało się, wpuszczając kod z forków EGCS do głównej gałęzi, ale projekt wciąż znajdował się w stagnacji. Szanse na postęp dawała dopiero wersja 3.0, rodząca się powoli w CVS projektu GNU.
Red Hat tymczasem pracował nad wydaniem wersji 7.0 swojego systemu operacyjnego i chciał, aby produkt ten był rewolucyjny i przełomowy. Poczyniono wiele postępów na drodze do zintegrowania jądra 2.4, co jednak nie udało się w momencie premiery. Zdecydowano się zatem na forsowanie zgodności z najnowszymi standardami C i C++. GCC nie był zgodny ze standardami ISO z roku 1999 dla tych języków. Tworzył też nieoptymalny kod dla C++ i sprawiał problemy na niektórych architekturach. Red Hat chciał, aby system 7.0 nie miał żadnych takich problemów. Zwłaszcza, że większość z nich była już w jakiś sposób rozwiązana.
git commit -m "rzeczy"
Podjęto decyzję, by pobrać wersję rozwojową "2.96" z CVS GNU (było to oznaczenie gałęzi "następnej wersji", którą miało być 3.0) i patchować ją tak długo, aż zostaną zlikwidowane wszystkie problemy z optymalizacją, niezgodnością ze standardami ISO i przenośnością. Ze względu na różny poziom dojrzałości łatek, było to bardzo wymagające zadanie. Źródłowy plik RPM (gcc-2.96-54.src.rpm) zawiera w swojej specyfikacji aż 81 plików *.patch modyfikujących zachowanie rozwojowej wersji GCC. Poprawek było tak dużo i były one integrowane w takim tempie, że dziennik zmian kilku ostatnich kompilacji zawierał po prostu wpis "dodano kolejne łatki", bez dokładniejszych opisów.
W rezultacie powstała wersja udająca GCC, ale o numerze wersji stosującym konwencję nazewniczą autorów. Stabilniejsza od rozwojowej gałęzi z CVS, ale niedojrzała. A przede wszystkim jednak, niekompatybilna binarnie zarówno z poprzednikiem (2.95) jak i następcą (3.0). Ta wspaniała decyzja sprawiła, że od teraz programy musiały być budowane razem ze swoimi bibliotekami, aby uniknąć problemów z dynamicznym linkowaniem. Jeżeli jakiś program korzystał z cudzej biblioteki "zatrzymanej w rozwoju" i niegdyś zbudowanej w 2.96, program celem zachowania zgodności musiał być również w niej budowany.
Wprowadzenie jakichkolwiek optymalizacji lub wyjątków specjalnie dla tej wersji GCC sprawiało, że po wydaniu wersji 3.0 często trzeba je było przerabiać. Wielu twórców nie zdecydowało się na to, ciągnąc za sobą przez lata antyczne środowiska budowania oraz wymagając oddzielnych, specjalnych wersji libc oraz libstdc++. Takie problemy były oczywiście niewidzialne póki 2.96 był "najnowszy", ale istniały przesłanki wskazujące na to, że fork GCC to fatalny pomysł.
Sprzeciw wobec faktów
GCC 2.96 nie potrafiło bowiem zbudować jądra Linuksa. Do budowania kernela, Red Hat 7.0 dostarczał oddzielny, dedykowany kompilator (!), ponieważ Linux był zgodny z GCC, a nie z ISO C. Red Hat założył najwyraźniej, że GNU powinno zadbać o to, by wersja 3.0 była zgodna ze standardami. Uznano, że presja ze strony komercyjnych integratorów GCC wymusi na GNU wprowadzenie zgodności. I że będzie to zgodność na modłę Red Hata: potencjalne problemy ze zgodnością ABI uznano za drobnostkę.
GNU, oraz reszta świata, uznały że standardy nie są święte i liczy się stan zastany, a nie wymarzona rzeczywistość ujęta w dokumentacji standaryzacyjnej. Red Hat stworzył całą dedykowaną stronę z opisem wymówek, w tonie "wszyscy się mylicie, nikt z was nie ma racji, a poza tym uważamy, że GNU CC 2.95 powinno zostać zniszczone". Nie zostały one jednak przyjęte przez rzeczywistość, która uparcie pozostała taka sama.
Dzisiaj problem z GCC 2.96 jest już naprawdę marginalny i mało kto na niego natrafia. Konieczność wciągania alternatywnego libc także należy już do przeszłości. Ale na przestrzeni lat, w świecie linuksowym pojawiały się zbliżone problemy w rozumowaniu. Jednym ze słynniejszych przykładów, ze względu na żywiołowe komentarze samego L. Torvaldsa, był owiany legendą błąd #638477.
Standards are paper
Zmiana w funkcji memcpy() biblioteki języka C wpłynęła na zachowanie mechanizmu pthreads, co objawiało się… problemami z odtwarzaniem dźwięku we wtyczce Adobe Flash Player. Zmiana ta miała zwiększyć wydajność operacji na pamięci i została przez upstream uznana za obowiązujące zachowanie libc. Programiści mieli pogodzić się z tym faktem i zmodyfikować swój kod. Winnym w takiej optyce jest Adobe, ponieważ dostarcza on kod "niezgodny z linuksowym libc".
Z takim poglądem nie zgodził się Torvalds, który stwierdził, że nie ma znaczenia, że libc kiedyś działało źle, a teraz działa dobrze. Liczy się to, że twórcy oprogramowania, bardzo popularnego, uzależniają się od owego "złego" zachowania i wszelkie zmiany skończą się awarią. Argument "no to trzeba sobie przebudować, bo standard się zmienił, a poza tym to chrzanić zamknięte oprogramowanie" nie przekonywał twórcy Linuksa.
Oczywiście wywołało to potyczkę filozoficzną. W ostateczności bowiem nie liczy się to, kto stworzy "najlepszy" program, wedle kryteriów teoretycznych, a kto stworzy program działający, choćby koszmarnie. Gdyby było inaczej, korzystalibyśmy dziś z zupełnie innego oprogramowania.