Jak języki wpłynęły na projekt procesora? [Zamknięte]

44

Jesteśmy często mówią , że sprzęt nie obchodzi, co język program jest napisany w jak to widzi tylko skompilowany kod binarny, jednak nie jest to cała prawda. Weźmy na przykład pokornego Z80; jego rozszerzenia do zestawu instrukcji 8080 obejmują instrukcje takie jak CPIR, które są przydatne do skanowania ciągów typu C (zakończonych NULL), np. do wykonania strlen(). Projektanci musieli zidentyfikować, że uruchamianie programów C (w przeciwieństwie do Pascala, gdzie długość łańcucha znajduje się w nagłówku) było czymś, do czego prawdopodobnie ich projekt mógłby zostać użyty. Innym klasycznym przykładem jest maszyna Lisp .

Jakie są inne przykłady? Np. Instrukcje, liczba i rodzaj rejestrów , tryby adresowania, które sprawiają, że dany procesor sprzyja konwencjom określonego języka? Szczególnie interesują mnie wersje tej samej rodziny.

Gajusz
źródło
3
Nie zapominaj, że Z-80 miał również instrukcję LDIR, bardzo przydatną podczas kopiowania łańcuchów, gdy znasz długość (jak w Pascalu, gdzie długość była przechowywana w nagłówku).
TMN
27
1. Z-80 został zaprojektowany w 1975 roku, kiedy Unix i C były niejasnym systemem operacyjnym i językiem na kilku komputerach, 3 lata przed pierwszą edycją K&R. 2. W Pascalu nic nie nakazuje, aby długość łańcucha była „w nagłówku”. 3. Ciągi znaków w CP / M, głównym wówczas systemie operacyjnym mikrokomputera, zostały zakończone znakiem „$”, a nie „\ 0”. CPIR może wyszukać dowolny znak. 4. CPIR jest dopasowany do CPDR (wyszukiwanie wstecz), a także innych instrukcji -IR i -DR. Wniosek: CPIR nie ma nic wspólnego z językiem programowania C. To tylko instrukcja wyszukiwania bajtów.
librik
4
Największą (i jedną z najbardziej irytujących dla projektantów sprzętu) z rzeczy wymuszonych przez C jest adresowanie bajtów. Procesory byłyby prostsze i szybsze bez tej obrzydliwości.
SK-logic
1
@ SK-logic: Chociaż standard POSIX wymaga adresowania bajtów, standard C nie. Każda implementacja, w której sizeof(int)równa się 1, musi wymagać charpodpisania tego typu (ponieważ intmusi być w stanie pomieścić wszystkie wartości typu char). Pisałem kod na komputerze, na którym chari intsą zarówno 16-bitowe liczby całkowite podpisane; największe trudności polegają na tym, że nie można używać związków do konwersji typów, a wydajne przechowywanie dużej liczby bajtów wymaga ręcznego pakowania i rozpakowywania. Te problemy są niewielkie w porównaniu z możliwością w C, że sizeof (int) == sizeof (long), ponieważ ...
supercat
2
... oznacza to, że nie ma standardowego typu, który gwarantowałby utrzymanie różnicy między dwiema unsigned intwartościami. C99 poprawił tę sytuację, ale przed C99 nie było gwarantowanego bezpiecznego jednoetapowego sposobu porównania potencjalnie ujemnej wartości z wartością typu unsigned int(trzeba by sprawdzić, czy liczba była ujemna przed wykonaniem porównania).
supercat

Odpowiedzi:

20

Istniejące odpowiedzi koncentrują się na zmianach ISA . Są też inne zmiany sprzętowe. Na przykład C ++ często używa vtables do wywołań wirtualnych. Począwszy od Pentium M , Intel ma komponent „pośredniego predykatora gałęzi”, który przyspiesza wirtualne wywołania funkcji.

MSalters
źródło
6
Architektura Berkeley RISC zawierała koncepcję „pliku rejestru”, więc zamiast funkcji „rozlewania” rejestrów na stos, do każdej funkcji przydzielono blok 8 rejestrów. To znacznie przyspieszyło obiektowy kod, ponieważ zwykle składa się z wielu wywołań metod do krótkich metod.
TMN
1
To nie jest poprawny przykład. Projekt „Tabela wskaźników funkcji” jest również używany w wielu scenariuszach z dynamicznym łączeniem, na przykład poprzez import i eksport DLL w Windows, a także używany w programach C. Chociaż myślę, że można argumentować, że pokazuje, że procesor jest zoptymalizowany do określonego zastosowania, nie jest on specyficzny dla języka.
DeadMG,
@DeadMG: Inne przypadki skorzystały, to prawda. Ale dopóki C ++ nie stało się popularne, nie wpłynęło to na projekty procesorów . I to było postawione pytanie. Podobnie TMN ma rację co do plików rejestrów. Asembler nie miał tak jasnej koncepcji funkcji. Funkcje, jak je dziś powszechnie rozumiemy, pochodzą z Algolu 60, dlatego możemy powiedzieć, że Algol 60 wpłynął na projekt pliku rejestru procesora.
MSalters
14

Zestaw instrukcji Intel 8086 zawiera odmianę „ret”, która dodaje wartość do wskaźnika stosu po usunięciu adresu zwrotnego. Jest to przydatne w przypadku wielu implementacji Pascala, w których funkcja wywołująca wypycha argumenty na stos przed wykonaniem wywołania funkcji, a następnie wyrzuca je. Jeśli procedura zaakceptuje np. Parametry o wartości czterech bajtów, może zakończyć się na „RET 0004”, aby wyczyścić stos. W przypadku braku takiej instrukcji, taka konwencja wywoływania prawdopodobnie wymagałaby, aby kod wrzucił adres zwrotny do rejestru, zaktualizował wskaźnik stosu, a następnie przeskoczył do tego rejestru.

Co ciekawe, większość kodu (w tym procedury systemu operacyjnego) na oryginalnym komputerze Macintosh używała konwencji wywoływania Pascala, pomimo braku instrukcji ułatwiającej w 68000. Korzystanie z tej konwencji wywoływania pozwoliło zaoszczędzić 2-4 bajty kodu w typowej witrynie wywoływania, ale wymagało dodatkowych 4-6 bajtów kodu w miejscu zwrotnym każdej funkcji, która pobierała parametry.

supercat
źródło
Istnieje również ENTERodpowiednik tego RET n...
herby
1
@herby: Nie sądzę, żeby ENTERistniał w oryginalnym 8086; przyszedł z późniejszymi procesorami. Przywołuje to jednak interesujący punkt: tryby adresowania oparte na BP są wyraźnie zaprojektowane wokół użycia stosu parametrów i miejsc, do których można uzyskać dostęp za pomocą wskaźnika ramki. Uważam tę konwencję za interesującą na wiele sposobów, zwłaszcza biorąc pod uwagę, że (1) czysty kod języka asemblera jest bardziej skłonny do używania wartości w rejestrach niż na stosie, ale (2) zalety adresowania [BP + nn] nad [SP + nn] adresowanie jest bardziej znaczące dla programów w asemblerze, które uzyskują dostęp do rzeczy na stosie, niż ...
supercat
... dla odręcznego kodu asemblera. Kompilator będzie ogólnie wiedział, dla każdej wygenerowanej instrukcji, jak SP i BP się porównują; na przykład jeśli SP to BP-8, kompilatorowi nie jest łatwiej adresować [BP + 12] niż [SP + 20]. Jeśli w rekompilacji kompilator musi dodać kolejny PUSH / POP wokół bloku kodu, może odpowiednio dostosować przesunięcia oparte na SP. Z drugiej strony, w ręcznym montażu, dodanie PUSH / POP bardziej wymagałoby ulepszenia kodu między nimi. Wskaźniki ramek są więc głównie zaletą połączonego kodu wysokiego poziomu / asm.
supercat
Być może możliwość ponownego użycia kodu bez jego ponownej kompilacji ma również marginalną użyteczność dla adresowania BP. I Bóg wie, czy instrukcje adresowania BP nie są szybsze w obwodzie niż instrukcje adresowane SP, ponieważ adresowanie BP jest swego rodzaju standardem ...
herby
3
@herby: Właściwie podejrzewam, że duża część powodów, dla których kompilatory zwykle używały wskaźników ramek, ma wiele wspólnego z debugowaniem. Debugowanie programu, który nie korzystał z takiej konwencji, wymagałoby od kompilatora wygenerowania - i użycia debuggera - pliku z listą przesunięcia SP-BP dla każdej instrukcji. Tak szczegółowe metadane są dziś powszechne (i są istotną częścią tego, co sprawia, że ​​języki zbierające śmieci są praktyczne), ale potrzebna ilość pamięci RAM byłaby nie do przyjęcia 30 lat temu.
supercat
10

Jednym z przykładów jest MIPS, który ma zarówno, jak addi odpowiednio addupułapkę i ignorowanie przepełnienia. (Również subi subu.) Potrzebował pierwszego rodzaju instrukcji dla języków takich jak Ada (myślę, że tak naprawdę nigdy nie użyłem Ady), które jawnie zajmują się przepełnieniami, a drugiego typu dla języków takich jak C, które ignorują przepełnienia.

Jeśli dobrze pamiętam, rzeczywisty procesor ma dodatkowe jednostki obwodów w jednostce ALU do śledzenia przelewów. Gdyby jedynym językiem, na którym zależało ludziom, było C, nie potrzebowałoby tego.

Tikhon Jelvis
źródło
Nie jestem pewien, czy są powiązane, ale te instrukcje są prawdopodobnie przydatne również w innych sytuacjach, takich jak bezpieczny przydział pamięci, tj. Jeśli przydzielasz nmemb*size+offsetbajty i musisz upewnić się, że nie zostanie przepełniony.
NikiC,
@NikC: Myślałem, że instrukcje addui subu(te, które nie sprawdzają przepełnień) to te, które zostały dodane, aby uszczęśliwić C. Oczywiście tak naprawdę nie wiem - omawialiśmy to tylko niejasno podczas wykładu i na pewno nie jestem ekspertem od architektury: P.
Tikhon Jelvis
O tak, myślałem na odwrót, przepraszam: /
NikiC
8

Seria Burroughs 5000 została zaprojektowana z myślą o wydajnej obsłudze ALGOL, a iAPX-432 Intela został zaprojektowany z myślą o wydajnym uruchamianiu Ady. Inmos Transputer miał swój własny język, Occam. Myślę, że procesor „śmigła” Parallax został zaprojektowany do programowania przy użyciu własnego wariantu BASIC.

To nie jest język, ale zestaw instrukcji VAX-11 ma jedną instrukcję ładowania kontekstu procesu, która została zaprojektowana na żądanie zespołu projektowego VMS. Nie pamiętam szczegółów, ale ISTR wymagało tak wielu instrukcji, aby wprowadzić poważny górny limit liczby procesów, które mogą zaplanować.

TMN
źródło
Co takiego sprawia, że ​​są one szczególnie odpowiednie? Np. Z jakiej funkcji iAPX korzysta szczególnie Ada?
Gajusz
ISTR, że obiekt docelowy Ada iAPX-432 starał się bardziej uratować nieudany projekt, dołączając go do czegoś z jeszcze dużymi oczekiwaniami niż cokolwiek innego.
AProgrammer
@AProgrammer: Jestem prawie pewien, że iAPX-432 został zaprojektowany od samego początku, aby używać Ady. Pamiętam nawet pogłoski, że Intel nie zamierza opublikować zestawu instrukcji, aby zniechęcić do programowania w asemblerze i zmusić ludzi do używania Ady do wszystkiego.
TMN
1
@TMN, projekt Intela 432 rozpoczął się w 1975 roku i został wprowadzony w 1981 roku (Wikipedia). Ironman (końcowe wymagania dla Ady), został opublikowany w styczniu 1977 r., A zielony został wybrany w maju 1979 r., Zmodyfikowany, a ostateczny wynik opublikowany jako standard wojskowy w lipcu 1980 r. Występuje problem z osią czasu stwierdzający, że iAPX-432 został zaprojektowany zacznij korzystać z Ady. (Jest to późny i typowy procesor „zamykający lukę semantyczną” ze zwykłymi wadami w czasie, gdy zaczęto poszukiwać alternatywnych rozwiązań; wprowadzanie go na rynek jako procesora Ada było próbą uratowania nieudanego projektu - ISTR, że nikt poza Intelem go nie używał )
AProgrammer
1
@AProgrammer: Hmmm, wygląda na to, że masz rację. Natknąłem się na ten artykuł od głównego architekta 432, a w podsumowaniu mówi: „To ścisłe połączenie architektury i języka nie miało miejsca, ponieważ 432 zostało zaprojektowane do wykonywania Ady - nie było”. Będę musiał wykopać swoją starą książkę 432 i zobaczyć, co ona mówi.
TMN
8

Jedną rzeczą, o której jak dotąd nikt nie wspominał, jest to, że postępy w optymalizacji kompilatora (gdzie język podstawowy jest w dużej mierze nieistotny) doprowadziły do ​​przejścia z zestawów instrukcji CISC (które zostały w dużej mierze zaprojektowane do kodowania przez ludzi) na zestawy instrukcji RISC (które były w dużej mierze zaprojektowane do kodowania przez kompilatory).

rockets4kids
źródło
5

Rodzina Motorola 68000 wprowadziła tryb automatycznego przyrostu , dzięki któremu kopiowanie danych przez procesor jest bardzo wydajne i kompaktowe.

[Zaktualizowany przykład]

to był jakiś kod c ++, który wpłynął na asembler 68000

while(someCondition)
    destination[destinationOffset++] = source[sourceOffset++]

zaimplementowane w konwencjonalnym asemblerze (pseudokod, zapomniałem poleceń asemblera 68000)

adressRegister1 = source
adressRegister2 = destination
while(someCondition) {
    move akku,(adressRegister1)
    move (adressRegister2), akku
    increment(adressRegister1, 1)
    increment(adressRegister2, 1)
}

z nowym trybem adresu stało się coś podobnego

adressRegister1 = source
adressRegister2 = destination
while(someCondition) {
    move akku,(adressRegister1++)
    move (adressRegister2++), akku
}

tylko dwie instrukcje na pętlę zamiast 4.

k3b
źródło
1
Jak wpłynęły na to konwencje danego języka?
Gajusz
patrz zaktualizowany przykład
k3b
Ach, przypomina mi o optymalizacji pętli DBxx w 68010.
Gaius
7
Właściwie myślę, że masz to do tyłu. Automatyczne adresowanie [in | de] kreacji było częścią zestawu instrukcji PDP-11, co prawdopodobnie wpłynęło na projekt C.
TMN 1'12
5

Komputer mainframe serii Z firmy IBM jest potomkiem IBM 360 z lat 60. XX wieku.

Podano tam kilka instrukcji, aby przyspieszyć programy COBOL i Fortran. Klasycznym przykładem jest BXLE- „Branch on Index Low or Equal”, który jest w większości forpętlą Fortrana lub COBOL-em PERFORM VARYING x from 1 by 1 until x > nenkapsulowanym w pojedynczej instrukcji.

Istnieje również cała rodzina spakowanych instrukcji dziesiętnych do obsługi arytmetyki stałoprzecinkowej dziesiętnej wspólnej w programach COBOL.

James Anderson
źródło
Myślę, że masz na myśli potomka .
Clockwork-Muse
@ X-Zero - ups! Wczesnym rankiem, za mało caffiene w systemie itp ...
James Anderson
1
Bardziej interesująca jest instrukcja powtarzania bloku TI 32050 DSP. Jego operandem jest adres instrukcji następującej po ostatniej w pętli; załadowanie rejestru zliczania pętli, a następnie wykonanie instrukcji powtarzania bloku spowoduje, że instrukcje do celu (ale bez uwzględnienia) będą powtarzane określoną liczbę razy. Bardzo mocno przypomina DOpętlę FORTRAN .
supercat
@ superuper Każdy godny polecenia DSP ma trzy funkcje: zerową pętlę napowietrzną, pojedynczą instrukcję mnożenia i akumulacji oraz pewnego rodzaju tryb adresowania z odwróconymi bitami. Prawie każdy znany algorytmowi DSP wykorzystuje pętle. Dwa najczęstsze algorytmy to filtr FIR, który jest pętlą wokół wielokrotnego gromadzenia, oraz FFT, dla których adresowanie z odwróceniem bitów ma kluczowe znaczenie. Wiele procesorów DSP obejmuje operację motylkową FFT z jedną instrukcją radix-2 lub podwójną funkcję mnożenia / dodawania, której można użyć do utworzenia motylkowej z jedną instrukcją.
John R. Strohm,
@ JohnR.Strohm: Każdy DSP, który widziałem, zawiera powtarzanie, mnożenie, ale nie wszystkie z nich zawierają bardziej uogólnione pętle zerowe. W rzeczywistości nie jestem do końca pewien, dlaczego takie pętle powinny być traktowane tylko jako funkcja „DSP”, ponieważ byłyby użyteczne również w wielu kodach „konwencjonalnych procesorów”.
supercat
3

Wczesne procesory Intel miały następujące funkcje, z których wiele jest obecnie przestarzałych w trybie 64-bitowym:

  • Instrukcje ENTER, LEAVE i RET nn [wczesne instrukcje wyraźnie mówiły, że zostały wprowadzone dla języków o strukturze blokowej, np. Pascal, który obsługuje procedury zagnieżdżone]
  • instrukcje przyspieszenia arytmetyki BCD (AAA, AAM itp.); obsługuje także BCD w x87
  • Instrukcje JCXZ i LOOP do implementacji zliczonych pętli
  • INTO, do generowania pułapki na przepełnieniu arytmetycznym (np. W Adzie)
  • XLAT do wyszukiwania tabel
  • BOUND do sprawdzania granic tablic

Flaga znaku, znajdująca się w rejestrze stanu wielu procesorów, istnieje w celu łatwego wykonywania arytmetyki podpisanej ORAZ niepodpisanej.

Zestaw instrukcji SSE 4.1 wprowadza instrukcje przetwarzania ciągów, zarówno zliczanych, jak i zakończonych zerami (PCMPESTR itp.)

Mogę sobie również wyobrazić, że wiele funkcji na poziomie systemu zaprojektowano w celu zapewnienia bezpieczeństwa skompilowanego kodu (sprawdzanie limitu segmentów, bramki wywołań z kopiowaniem parametrów itp.)

zvrba
źródło
3

Niektóre procesory ARM, głównie te w urządzeniach mobilnych, obejmują (d) rozszerzenie Jazelle, które jest sprzętowym interpretatorem JVM; bezpośrednio interpretuje kod bajtowy Java. JVM obsługujący technologię Jazelle może wykorzystać sprzęt do przyspieszenia wykonywania i wyeliminowania dużej części JIT, ale powrót do oprogramowania VM jest nadal zapewniony, jeśli nie można interpretować kodu bajtowego na chipie.

Procesory z taką jednostką zawierają instrukcję BXJ, która wprowadza procesor w specjalny „tryb Jazelle”, lub jeśli aktywacja jednostki nie powiodła się, jest to po prostu interpretowane jako normalna instrukcja rozgałęzienia. Urządzenie ponownie wykorzystuje rejestry ARM do utrzymania stanu JVM.

Następcą technologii Jazelle jest ThumbEE

usoban
źródło
2

O ile wiem, w przeszłości było to bardziej powszechne.

Istnieje sesja pytań, w której James Gosling powiedział, że są ludzie, którzy próbują stworzyć sprzęt, który mógłby lepiej radzić sobie z kodem bajtowym JVM, ale wtedy ci ludzie mogliby znaleźć sposób, aby to zrobić za pomocą zwykłego „generycznego” intel x86 (może kompilując kod bajtowy w jakiś sprytny sposób).

Wspomniał, że korzystanie z popularnego popularnego mikroukładu (takiego jak dane wywiadowcze) ma przewagę, ponieważ ma dużą korporację, która rzuca ogromne ilości pieniędzy na produkt.

Film warto sprawdzić. Mówi o tym w 19 lub 20 minucie.

Pedro Henrique A. Oliveira
źródło
2

Przeprowadziłem szybkie wyszukiwanie strony i wydaje się, że nikt nie wspomniał o procesorach opracowanych specjalnie do wykonywania Forth . Język programowania Forth jest oparty na stosie, zwarty i stosowany w systemach sterowania.

Paddy3118
źródło
2

Intel iAPX CPU został zaprojektowany specjalnie dla języków oo. Jednak nie całkiem wyszło.

IAPX 432 ( Intel Procesor zaawansowane architektura ) był pierwszy 32-bitowy mikroprocesor konstrukcja Intela, wprowadzony w 1981 roku jako zespół trzech układów scalonych. Miał być głównym projektem Intela w latach 80., wdrażającym wiele zaawansowanych funkcji wielozadaniowości i zarządzania pamięcią. Projekt został więc nazwany Micromainframe ...

IAPX 432 został „zaprojektowany do programowania w całości w językach wysokiego poziomu” , przy czym Ada jest podstawowa i wspiera programowanie obiektowe oraz odśmiecanie bezpośrednio w sprzęcie i mikrokodzie . Bezpośrednia obsługa różnych struktur danych miała również umożliwić wdrożenie nowoczesnych systemów operacyjnych dla iAPX 432 przy użyciu znacznie mniej kodu programu niż w przypadku zwykłych procesorów. Te właściwości i cechy spowodowały, że konstrukcja sprzętu i mikrokodu była znacznie bardziej złożona niż większość procesorów tamtej epoki, zwłaszcza mikroprocesorów.

Wykorzystując technologię półprzewodników swoich czasów, inżynierowie Intela nie byli w stanie przełożyć projektu na bardzo wydajną pierwszą implementację. Wraz z brakiem optymalizacji w przedwczesnym kompilatorze Ada, przyczyniło się to do raczej powolnych, ale drogich systemów komputerowych, wykonujących typowe testy porównawcze z prędkością około 1/4 prędkości nowego układu 80286 przy tej samej częstotliwości taktowania (na początku 1982 r.).

Ta początkowa różnica w wydajności w stosunku do raczej niskiego profilu i taniej linii 8086 była prawdopodobnie głównym powodem, dla którego plan Intela dotyczący zastąpienia tego ostatniego (znanego później jako x86) iAPX 432 nie powiódł się. Chociaż inżynierowie dostrzegli sposoby ulepszenia projektu nowej generacji, architektura iAPX 432 zaczęła być teraz postrzegana bardziej jako koszt wdrożenia niż jako uproszczenie, jakim miała być.

Projekt iAPX 432 był komercyjną porażką Intela ...

tylko przelatując
źródło
Czytając artykuł, wydaje się, że wiele aspektów projektu może być użytecznych w obiektowych ramach, takich jak obecnie popularne. Architektura wykorzystująca kombinację 32-bitowego identyfikatora obiektu i 32-bitowego przesunięcia może w wielu przypadkach zaoferować lepszą wydajność buforowania niż ta, w której wszystkie identyfikatory obiektów miały 64 bity (w większości przypadków aplikacja wykorzystująca miliardy obiektów lepiej służyć, zamiast tego mieć więcej, większych; taki, który przechowywałby miliardy bajtów w jednym obiekcie, lepiej byłoby podzielić go na mniejsze obiekty
supercat,
1

68000 miał MOVEM, który był najbardziej odpowiedni do wypychania wielu rejestrów na stos w jednej instrukcji, czego oczekiwało wiele języków.

Jeśli widziałeś MOVEM (MOVE Multiple) poprzedzający JSR (Jump SubRoutine) w całym kodzie, to ogólnie wiedziałeś, że masz do czynienia z kodem zgodnym z C.

MOVEM zezwalał na automatyczną inkrementację rejestru docelowego, umożliwiając każdemu użyciu dalsze układanie w stos na miejscu docelowym lub usuwanie ze stosu w przypadku automatycznego dekrementacji.

http://68k.hax.com/MOVEM

Myztry
źródło
1

Architektura AVR firmy Atmel została całkowicie zaprojektowana od podstaw, aby nadawała się do programowania w C. Na przykład ta nota aplikacyjna jest bardziej szczegółowa.

IMO jest to ściśle związane z doskonałą odpowiedzią rockets4kids , przy czym wczesne PIC16 zostały opracowane do bezpośredniego programowania asemblera (łącznie 40 instrukcji), a późniejsze rodziny celowały w C.

Vorac
źródło
1

Podczas projektowania koprocesora numerycznego 8087 dość często języki przeprowadzały matematykę zmiennoprzecinkową przy użyciu typu o najwyższej precyzji i tylko zaokrąglały wynik w celu uzyskania niższej precyzji, przypisując go do zmiennej o niższej precyzji. Na przykład w oryginalnym standardzie C sekwencja:

float a = 16777216, b = 0.125, c = -16777216;
float d = a+b+c;

będzie promować ai baby doubledodać je promować c, aby doubledodać go, a następnie zapisać wynik zaokrągla się float. Mimo że w wielu przypadkach kompilator szybciej generowałby kod, który wykonywałby operacje bezpośrednio na typie float, łatwiej było mieć zestaw procedur zmiennoprzecinkowych, które działałyby tylko na typie double, wraz z procedurami do konwersji na / od float, niż mieć osobne zestawy procedur do obsługi operacji na floati double. 8087 został zaprojektowany w oparciu o takie podejście do arytmetyki, wykonując wszystkie operacje arytmetyczne przy użyciu 80-bitowego typu zmiennoprzecinkowego [prawdopodobnie wybrano 80 bitów, ponieważ:

  1. Na wielu 16- i 32-bitowych procesorach szybsza jest praca z 64-bitową mantysą i osobnym wykładnikiem niż praca z wartością, która dzieli bajt między mantysą i wykładnikiem.

  2. Bardzo trudno jest wykonać obliczenia, które są dokładne z pełną precyzją używanych typów numerycznych; jeśli ktoś próbuje np. obliczyć coś takiego jak log10 (x), łatwiej i szybciej jest obliczyć wynik z dokładnością do 100ulp typu 80-bitowego niż obliczyć wynik z dokładnością do 1ulp 64-bitowego typ, a zaokrąglenie pierwszego wyniku do 64-bitowej precyzji da 64-bitową wartość, która jest dokładniejsza niż druga.

Niestety, przyszłe wersje języka zmieniły semantykę działania typów zmiennoprzecinkowych; podczas gdy semantyka 8087 byłaby bardzo ładna, gdyby języki wspierały je konsekwentnie, jeśli zwracane floatbyłyby funkcje f1 (), f2 () itd. , wielu autorów kompilatora wziąłoby na siebie long doublealias 64-bitowego typu podwójnego zamiast 80-bitowego typu kompilatora (i nie zapewniają żadnych innych sposobów tworzenia 80-bitowych zmiennych) i do arbitralnej oceny czegoś takiego:

double f = f1()*f2() - f3()*f4();

na dowolny z poniższych sposobów:

double f = (float)(f1()*f2()) - (extended_double)f3()*f4();
double f = (extended_double)f1()*f2() - (float)(f3()*f4());
double f = (float)(f1()*f2()) - (float)(f3()*f4());
double f = (extended_double)f1()*f2() - (extended_double)f3()*f4();

Zauważ, że jeśli f3 i f4 zwracają te same wartości, co odpowiednio f1 i f2, oryginalne wyrażenie powinno wyraźnie zwracać zero, ale wiele z ostatnich wyrażeń może nie. Doprowadziło to do tego, że ludzie potępiali „dodatkową precyzję” 8087, mimo że ostatnie sformułowanie byłoby na ogół lepsze od trzeciego i - z kodem, który odpowiednio używał rozszerzonego podwójnego typu - rzadko bywało gorsze.

W międzyczasie Intel zareagował na trend języka (niefortunny IMHO) polegający na wymuszaniu zaokrąglania wyników pośrednich do precyzji operandów, projektując ich późniejsze procesory, tak aby faworyzować to zachowanie, ze szkodą dla kodu, który skorzystałby na zastosowaniu wyższej precyzja obliczeń pośrednich.

supercat
źródło
Pamiętaj, że masz już odpowiedź ( powyżej ) w tym poście. Czy są to odpowiedzi, które można / należy połączyć w jedno?
@MichaelT: Nie sądzę - jedna dotyczy projektowania stosu, a druga semantyki zmiennoprzecinkowej.
supercat
Tylko się upewniam. Osobiście uważam, że byłoby możliwe udzielenie jednej, silniejszej odpowiedzi (użycie nagłówków do oddzielenia sekcji), ale takie jest moje zdanie. Możesz nadal używać nagłówków, aby jasno określić na górze, co adresuje każda część odpowiedzi ( ## How the stack changed the processori ## How floating point changed the processor), aby ludzie mogli uzyskać właściwe nastawienie podczas czytania i mniej prawdopodobne, że pomyślisz, że jesteś nieobecny w odpowiedziach lub odpowiedziach takie same (r podobne) odpowiedzi.
@MichaelT: Dwie odpowiedzi są na tyle rozłączne, że uważam, że należy je głosować osobno. Chociaż 80486 wchłonął funkcje wcześniej wykonywane przez 8087/80287/80387, 8086 i 8087 zostały zaprojektowane jako osobne układy o prawie niezależnej architekturze. Chociaż oba uruchamiały kod ze wspólnego strumienia instrukcji, który został obsłużony przez to, że 8086 traktował pewne sekwencje bajtów jako żądania generowania żądań odczytu / zapisu adresu, ignorując magistralę danych, i 8087 ignorował wszystko inne, co się działo.
supercat