Egzotyczne architektury, o które dbają komitety normalizacyjne

154

Wiem, że standardy C i C ++ pozostawiają wiele aspektów implementacji języka tylko dlatego, że gdyby istniała architektura o innych cechach, byłoby bardzo trudne lub niemożliwe napisanie dla niej kompilatora zgodnego ze standardami.

Wiem, że 40 lat temu każdy komputer miał swoją własną, unikalną specyfikację. Jednak nie znam żadnej architektury używanej dzisiaj, gdzie:

  • CHAR_BIT != 8
  • signed nie jest uzupełnieniem do dwóch (słyszałem, że Java miała z tym problemy).
  • Liczba zmiennoprzecinkowa nie jest zgodna z IEEE 754 (Edycja: miałem na myśli „nie w kodowaniu binarnym IEEE 754”).

Pytam, ponieważ często wyjaśniam ludziom, że to dobrze, że C ++ nie narzuca żadnych innych aspektów niskiego poziomu, takich jak typy o ustalonej wielkości . Jest to dobre, ponieważ w przeciwieństwie do `` innych języków '' sprawia, że ​​twój kod jest przenośny, gdy jest używany poprawnie (Edycja: ponieważ można go przenieść na więcej architektur bez konieczności emulacji niskopoziomowych aspektów maszyny, takich jak np. Arytmetyka dopełniająca do dwóch na architekturze znak + wielkość) . Ale czuję się źle, że sam nie mogę wskazać żadnej konkretnej architektury.

A więc pytanie brzmi: jakie architektury wykazują powyższe właściwości?

uint*_ts są opcjonalne.

Yakov Galka
źródło
9
Myślę, że masz to wstecz. Gdyby C ++ narzucał, powiedzmy, uzupełnienie do dwóch liczb całkowitych ze znakiem, uczyniłoby to kod C ++ bardziej przenośnym, a nie mniejszym. Pytanie, dlaczego komisja normalizacyjna C ++ nie upoważnia tego, to inna sprawa. Zwłaszcza, że ​​wbrew temu, co mówisz, nie byłoby niemożliwe napisanie kompilatora dla niestandardowej architektury, zawsze możesz zasymulować arytmetykę uzupełniającą 8-bitowe znaki lub dwójki, nawet jeśli Twoja platforma nie obsługuje jej bezpośrednio.
john
8
@john: byłoby to niepraktyczne, więc niestandardowy zgodny kompilator wygenerowałby szybszy kod niż zgodny. Nadal nie rozumiem, jak to uczyni twój kod bardziej przenośnym.
Yakov Galka
4
Jestem pewien, że prawdziwym powodem, dla którego standard jest taki, nie jest to, że jest to idealne rozwiązanie. Ale zamiast tego dzieje się tak, ponieważ w momencie pisania standardu istniało już wiele kompilatorów C i C ++, a komitet normalizacyjny nie chciał odrzucać istniejących kompilatorów.
john
4
@john: Wątpię, czy „ułatwienie pisarzom kompilatorów” jest priorytetem podczas tworzenia standardu C ++ (gdyby tak było, wykonaliby okropną robotę, ponieważ C ++ jest jednym z najtrudniejszych języków do przeanalizowania i inne aspekty język nie ułatwia też twórców kompilatorów). Wydajność, obsługa szerokiej platformy i kompatybilność wsteczna są jednak dość ważne. I wszystkie te trzy osoby ucierpiałyby, gdyby wspomniane ograniczenie (a) zostałyby dodane do normy.
Sander De Dycker,
5
Nie chodzi o kompilator, ale sprzęt. C ++ pozostawia pewne rzeczy nieokreślone, aby umożliwić bezpośrednie użycie funkcji sprzętowych. Twoje aplikacje telefoniczne i tak nie będą działać na komputerze mainframe, więc nie ma możliwości przenoszenia, jakkolwiek zgodny jest kod.
Bo Persson,

Odpowiedzi:

114

Spójrz na to

Serwery Unisys ClearPath Dorado

oferując kompatybilność wsteczną dla osób, które nie migrowały jeszcze całego oprogramowania Univac.

Kluczowe punkty:

  • 36-bitowe słowa
  • CHAR_BIT == 9
  • komplement
  • 72-bitowy zmiennoprzecinkowy inny niż IEEE
  • oddzielna przestrzeń adresowa dla kodu i danych
  • adresowane słowami
  • brak dedykowanego wskaźnika stosu

Nie wiem, czy oferują kompilator C ++, ale mogliby .


A teraz pojawił się link do najnowszego wydania ich podręcznika C:

Podręcznik programowania kompilatora Unisys C.

Rozdział 4.5 zawiera tabelę typów danych z 9, 18, 36 i 72 bitami.

rozmiar i zakres typów danych w kompilatorze USC C.

Bo Persson
źródło
13
Wydaje mi się, że użycie void * w tej architekturze jest piekielne.
luiscubal
13
@ybungalobill - Wierzę char*i void*muszą być tej samej wielkości i wystarczająco duże, aby pomieścić dowolny inny wskaźnik. Reszta zależy od realizacji.
Bo Persson
22
@ybungalobill: Na starych kompilatorach Win16 zwykłe wskaźniki były blisko wskaźników i zawierały tylko 16-bitowe przesunięcie, więc sizeof(int*) == 2, ale dalekie wskaźniki miały również 16-bitowy selektor, więc sizeof(void*) == 4.
Adam Rosenfield
10
Istnieje lub kiedyś istniał podręcznik on-line dotyczący ich kompilatora C ++. Warto również zauważyć, że jest to tylko jedna z architektur mainframe Unisys: druga to architektura 48-bitowa ze znacznikiem wielkości ze znakiem (dla której znalazłem tylko podręcznik C, a nie C ++). Co do reszty: nie sądzę, żeby sizeof(int*) != sizeof(char*)tutaj: oba mają 36 bitów. Ale selektor bajtów w char*znajduje się na bitach wyższego rzędu i jest ignorowany int*. (Użyłem jednak innych maszyn, gdzie `sizeof (char *)> sizeof (int *).)
James Kanze,
16
@Adam Rosenfield W 16-bitowych kompilatorach MS / DOS miałeś różne „tryby”, a wskaźniki danych niekoniecznie miały taki sam rozmiar jak wskaźniki funkcji. Ale przynajmniej w tych, których użyłem, wszystkie wskaźniki danych (w tym void*) zawsze miały ten sam rozmiar. (Oczywiście nie można było przekonwertować wskaźnika funkcji na void*, ponieważ void*może być mniejszy. Ale zgodnie ze standardem nie można tego zrobić również dzisiaj.)
James Kanze,
51

Żadne z twoich założeń nie odnosi się do komputerów mainframe. Na początek nie znam komputera mainframe, który używa standardu IEEE 754: IBM używa liczby zmiennoprzecinkowej o podstawie 16, a oba komputery mainframe Unisys używają podstawy 8. Maszyny Unisys są nieco wyjątkowe pod wieloma innymi względami: Bo wspomniał o 2200 architektura, ale architektura MPS jest jeszcze dziwniejsza: 48-bitowe słowa tagowane. (To, czy słowo jest wskaźnikiem, czy nie, zależy od bitu w słowie). Reprezentacje liczbowe są zaprojektowane w taki sposób, że nie ma rzeczywistego rozróżnienia między arytmetyką zmiennoprzecinkową i całkową: zmiennoprzecinkowa ma podstawę 8; nie wymaga normalizacji iw przeciwieństwie do wszystkich innych widzianych przeze mnie liczb zmiennoprzecinkowych, umieszcza ułamek dziesiętny po prawej stronie mantysy, a nie po lewej, i używa wartości ze znakiem jako wykładnika (oprócz mantysy). Z wynikami, że całkowita wartość zmiennoprzecinkowa ma (lub może mieć) dokładnie taką samą reprezentację bitową jak liczba całkowita ze znakiem. I nie ma instrukcji arytmetycznych zmiennoprzecinkowych: jeśli wykładniki obu wartości są równe 0, instrukcja wykonuje arytmetykę całkową, w przeciwnym razie wykonuje arytmetykę zmiennoprzecinkową. (Kontynuacja filozofii tagowania w architekturze.) Co oznacza, że ​​podczasint może zajmować 48 bitów, 8 z nich musi wynosić 0, inaczej wartość nie będzie traktowana jako liczba całkowita.

James Kanze
źródło
4
Komputery mainframe IBM (z / Architecture) obsługują zmiennoprzecinkowe IEE754.
Nikita Nemkin
1
fyi, zobacz ten komentarz na Twitterze
Shafik Yaghmour
6
@Nikita - Teraz robią . Początkowo był to (drogi) dodatek do obsługi języka Java.
Bo Persson
42

Pełna zgodność ze standardem IEEE 754 jest rzadkością w implementacjach zmiennoprzecinkowych. A osłabienie specyfikacji w tym zakresie pozwala na wiele optymalizacji.

Na przykład obsługa subnormy różni się między x87 i SSE.

Optymalizacje, takie jak łączenie mnożenia i dodawania, które były oddzielne w kodzie źródłowym, również nieznacznie zmieniają wyniki, ale są dobrą optymalizacją na niektórych architekturach.

Lub na x86 ścisła zgodność ze standardem IEEE może wymagać ustawienia pewnych flag lub dodatkowych transferów między rejestrami zmiennoprzecinkowymi i normalną pamięcią, aby wymusić użycie określonego typu zmiennoprzecinkowego zamiast wewnętrznych 80-bitowych wartości zmiennoprzecinkowych.

Niektóre platformy nie mają żadnych pływaków sprzętowych i dlatego muszą emulować je w oprogramowaniu. Niektóre wymagania IEEE 754 mogą być drogie do wdrożenia w oprogramowaniu. W szczególności problemem mogą być zasady zaokrąglania.

Mój wniosek jest taki, że nie potrzebujesz egzotycznych architektur, aby znaleźć się w sytuacjach, w których nie zawsze chcesz zagwarantować ścisłą zgodność z IEEE. Z tego powodu niewiele języków programowania gwarantuje ścisłą zgodność z IEEE.

CodesInChaos
źródło
7
Innym „egzotycznym” zestawem sprzętu są komputery mainframe IBM, w których format zmiennoprzecinkowy poprzedza standard IEEE. W przeciwieństwie do Javy, C ++ może nadal korzystać z istniejącego sprzętu.
Bo Persson,
5
IEEE 754 nie jest w pełni obsługiwany przez procesory graficzne.
kerem
3
Brak ścisłej zgodności ze standardem IEEE 754 jest dla niektórych kłopotliwy, ale nie sądzę, aby był w zakresie kwestii, na których OP naprawdę zależy.
Omnifarious
3
@Matthieu Ponieważ jest to również oznaczone jako „C”, powinienem wspomnieć o analizatorze C, który może podać wszystkie wartości, które może przyjąć twój program zmiennoprzecinkowy, z 80-bitowymi rejestrami zmiennoprzecinkowymi przelanymi do pamięci na kaprys kompilatora C. blog.frama-c.com/index.php?post/2011/03/03/cosine-for-real
Pascal Cuoq
2
@MatthieuM .: Szkoda, że ​​ISO / ANSI nie pozwalało parametrom wariadycznym na określenie minimalnych / maksymalnych rozmiarów argumentów zmiennoprzecinkowych i całkowitych; gdyby tak było, 80-bitowy long doublemógłby być użytecznym i długowiecznym typem, ponieważ jedynym prawdziwym problemem było to, że źle działa printf. Fakt, że rozszerzona podwójna przechowuje wiodącą 1 wyraźnie przyspiesza obliczenia w systemach innych niż FPU, a także eliminowałby potrzebę specjalnej obsługi denormali w dowolnym kontekście innym niż konwersje do / z innych typów. Szkoda, że ​​C printfwszystko zepsuło.
supercat
40

Znalazłem ten link zawierający listę niektórych systemów, w których CHAR_BIT != 8. Zawierają

niektóre TI DSP mają CHAR_BIT == 16

Chip BlueCore-5 (chip Bluetooth firmy Cambridge Silicon Radio), który ma CHAR_BIT == 16.

I oczywiście pojawia się pytanie dotyczące przepełnienia stosu: jakie platformy mają coś innego niż 8-bitowe znaki

Jeśli chodzi o systemy z dopełnieniem innym niż dwa, jest ciekawa lektura na temat comp.lang.c ++. Moderated . Podsumowując: istnieją platformy, które mają swoją reprezentację dopełnienia lub znaku i wielkości.

dcn
źródło
5
Analog Devices 32-bitowy SHARC DSP ma CHAR_BIT=32, a Texas Instruments DSP z TMS32F28xx ma CHAR_BIT=16. GCC 3.2 dla PDP-10 ma CHAR_BIT=9. Myślę, że S / 360 może mieć też nie-8-bitowy znak.
osgx
1
Nadal chciałbym mieć przykład architektur „dopełniających się do dwóch”. Zwłaszcza, że ​​zdarzyło się, że CHAR_BITSjest to częściowy duplikat.
Yakov Galka
Procesory DSP TI mają 16-bitowe znaki tylko dlatego, że wybrali je realizatorzy (wymagałoby to nieco więcej pracy, aby działało poprawnie, ale nie jest to absurdalnie trudne IIRC - prawdopodobnie tylko kilka "dziur" w rusztowaniu kodującym w bazowym kompilatorze) . Więc to nie jest jakiś głęboki powód architektoniczny. Kod C działa na abstrakcyjnej maszynie. Jeśli wszystko co masz to 16-bitowe INT, zapisz po dwa znaki w każdym i dodaj scalanie odczytu, modyfikacji i zapisu do optymalizatora wizjera (przynajmniej). Jasne, to więcej pracy, ale spójrz tylko, ile pracy ma każdy, aby poradzić sobie z takimi dziwnymi typami w miejscach, w których nigdy się nie pojawią. Fuj.
Przywróć Monikę
24

Jestem prawie pewien, że systemy VAX są nadal w użyciu. Nie obsługują zmiennoprzecinkowych IEEE; używają własnych formatów. Alpha obsługuje formaty zmiennoprzecinkowe VAX i IEEE.

Maszyny wektorów Cray, takie jak T90, również mają swój własny format zmiennoprzecinkowy, chociaż nowsze systemy Cray używają IEEE. (T90, którego używałem, został wycofany z eksploatacji kilka lat temu; nie wiem, czy któryś z nich jest nadal w użyciu.)

T90 miał również / ma kilka interesujących reprezentacji wskaźników i liczb całkowitych. Natywny adres może wskazywać tylko na słowo 64-bitowe. Kompilatory C i C ++ miały CHAR_BIT == 8 (konieczne, ponieważ działał Unicos, odmiana Uniksa i musiał współpracować z innymi systemami), ale natywny adres mógł wskazywać tylko na słowo 64-bitowe. Wszystkie operacje na poziomie bajtów zostały zsyntetyzowane przez kompilator i a void*lub char*zapisane przesunięcie bajtowe w trzech najwyższych bitach słowa. Myślę, że niektóre typy liczb całkowitych miały bity wypełnienia.

Innym przykładem są komputery mainframe IBM.

Z drugiej strony, te konkretne systemy nie muszą koniecznie wykluczać zmian w standardzie językowym. Cray nie wykazywał żadnego szczególnego zainteresowania aktualizacją swojego kompilatora C do C99; przypuszczalnie to samo dotyczy kompilatora C ++. To może być uzasadnione, aby zaostrzyć wymagania dla hostowanych implementacjach, takie jak wymóg CHAR_BIT == 8 formacie IEEE zmiennoprzecinkowych jeśli nie pełne semantyki i 2's-dopełniacza bez bitów wypełniających dla podpisanych liczb całkowitych. Stare systemy mogą nadal obsługiwać wcześniejsze standardy językowe (C90 nie umarł, gdy pojawił się C99), a wymagania mogą być luźniejsze dla implementacji wolnostojących (systemów wbudowanych), takich jak procesory DSP.

Z drugiej strony, mogą istnieć dobre powody, dla których przyszłe systemy będą robić rzeczy, które dziś byłyby uważane za egzotyczne.

Keith Thompson
źródło
6
Na koniec dobra uwaga na temat tego, jak zbyt surowe normy uniemożliwiają innowacje. Kiedy otrzymamy komputery kwantowe (lub organiczne) ze stanami trójargumentowymi, wymagania arytmetyczne modulo dla unsignedtypów całkowitych będą dużym problemem , podczas gdy arytmetyka ze znakiem będzie w porządku.
Ben Voigt
@BenVoigt Dlaczego arytmetyka bez znaku jest uciążliwa? Czy dodawanie modulo 3 ^ n na tych komputerach nie jest możliwe?
phuclv
2
@ LưuVĩnhPhúc: Dokładnie o to chodzi, z operacjami sprzętowymi wykonywanymi modulo 3 ** n, dostarczając C ++ typy bez znaku, których operacje są zdefiniowane modulo 2 ** n, będzie trudne.
Ben Voigt
2
Znam jeden VAX 11/780 wciąż używany jako host dla kompilatora krzyżowego, którego celem jest wyspecjalizowany system wbudowany z zastrzeżoną architekturą. Aby utrzymać ten konkretny VAX, opiekunowie zwracają się do muzeów po części zamienne.
Peter
2
@Keith - technicznie jedyną przeszkodą jest przejście przez proces dostarczenia dowodów, które spełnią wymogi regulacyjne, ponieważ docelowy system wbudowany ma wysoką krytyczność. Istnieje jednak wiele przeszkód pozatechnicznych (polityka organizacyjna itp.), Które jednak były dotychczas nie do pokonania. Obecnie łatwiej jest zamontować skrzynkę do raidowania muzeów niż zaktualizować hosta.
Peter
16

CHAR_BITS

Według kodu źródłowego GCC :

CHAR_BITjest 16bity 1750a , dsp16xx architekturach.
CHAR_BITto 24bity dla architektury dsp56k .
CHAR_BITto 32bity dla architektury c4x .

Możesz łatwo znaleźć więcej, wykonując:

find $GCC_SOURCE_TREE -type f | xargs grep "#define CHAR_TYPE_SIZE"

lub

find $GCC_SOURCE_TREE -type f | xargs grep "#define BITS_PER_UNIT"

jeśli CHAR_TYPE_SIZEjest odpowiednio zdefiniowana.

Zgodność z IEEE 754

Jeśli architektura docelowa nie obsługuje instrukcji zmiennoprzecinkowych, gcc może wygenerować oprogramowanie awaryjne, które nie jest domyślnie zgodne ze standardem. Co więcej, -funsafe-math-optimizationsmożna użyć specjalnych opcji (takich jak witch również wyłącza zachowywanie znaków dla zer).

ivaigult
źródło
3
przegłosowano za proste skierowanie OP, aby spojrzał na źródło popularnego kompilatora; to jest definicja RFTM w tym przypadku, więc to powinno być pierwsze miejsce, na które ludzie patrzą.
underscore_d
9

Reprezentacja binarna IEEE 754 była do niedawna rzadkością na procesorach graficznych, patrz Paranoja zmiennoprzecinkowa GPU .

EDYCJA: w komentarzach pojawiło się pytanie, czy zmiennoprzecinkowy GPU ma znaczenie dla zwykłego programowania komputerowego, niezwiązanego z grafiką. O tak! Większość obliczeń przemysłowych o wysokiej wydajności odbywa się obecnie na procesorach graficznych; lista obejmuje sztuczną inteligencję, eksplorację danych, sieci neuronowe, symulacje fizyczne, prognozę pogody i wiele więcej. Jedno z linków w komentarzach pokazuje dlaczego: przewaga zmiennoprzecinkowa rzędu wielkości GPU.

Kolejna rzecz, którą chciałbym dodać, która jest bardziej odpowiednia dla pytania OP: co ludzie robili 10-15 lat temu, kiedy zmiennoprzecinkowy GPU nie był IEEE i kiedy nie było API, takiego jak dzisiejszy OpenCL lub CUDA do programowania GPU? Wierzcie lub nie, pionierom obliczeń GPU udało się zaprogramować układy GPU bez interfejsu API, aby to zrobić ! Spotkałem jednego z nich w swojej firmie. Oto, co zrobił: zakodował dane, których potrzebował do obliczenia, jako obraz z pikselami reprezentującymi wartości, nad którymi pracował, a następnie użył OpenGL do wykonania potrzebnych operacji (takich jak „rozmycie gaussowskie”, aby przedstawić splot z rozkładem normalnym itp.) i zdekodował wynikowy obraz z powrotem do tablicy wyników. A to wciąż było szybsze niż użycie procesora!

Takie rzeczy skłoniły NVidię do ostatecznego dostosowania swoich wewnętrznych danych binarnych do IEEE i wprowadzenia interfejsu API zorientowanego na obliczenia, a nie na manipulację obrazem.

Michael
źródło
Jakie znaczenie mają GPU? (a) Ta strona wydaje się bardzo nieaktualna. (b) Do dziś nie można programować procesorów graficznych w C: ponieważ C obsługuje takie rzeczy, jak funkcje rekurencyjne, których GPU, o ile wiem, nie obsługują. Więc nie możesz nawet napisać kompilatora, jeśli chcesz.
Yakov Galka
1
@ybungalobill, przeniesienie powtarzalnej pracy na GPU jest obecnie preferowaną metodą obliczeń na dużą skalę . W rzeczywistości obecnie pracuję nad jednym w C ++. Na szczęście pracujemy tylko z procesorami graficznymi NVidia CUDA, które mają binarną reprezentację pływaków zgodną ze standardem IEEE 754.
Michael
Nie mówię, że GPU nie są używane do obliczeń GPU. Powiedziałem, że tak naprawdę nie programuje się jądra w C, pomimo podobieństwa składni. Czy możesz wykonać int f(int n) { return n <= 1 ? 1 : n * f(n-1); }w CUDA? Jeśli nie, to GPU nie są istotne dla tego pytania (które dotyczy komitetów C i C ++).
Yakov Galka
6
@ybungalobill: kilka odpowiedzi na to pytanie. Po pierwsze, CUDA obsługuje C, C ++ i Fortran . Zobacz to samo łącze, aby zobaczyć ogromną przewagę wydajności 2048-wątkowych procesorów graficznych w porównaniu z typowym 8-wątkowym procesorem. Po drugie, prawda, obsługiwane są tylko podzbiory (choć duże) tych języków, w tym brak obsługi rekurencji odpowiedniej dla modelu programowania CUDA (zwanej „dynamicznym równoległością”) do wersji CUDA 5.0. Po trzecie, rekurencje można zwykle zastąpić pętlami, które i tak są niezbędne do wykonywania wielowątkowości.
Michael,