Organizacja i architektura systemu komputerowego Tom 2 - William Stallings


Reflow text when sidebars are open.
Dane oryginału
Computer Organization and Architecture, William Stallings, ISBN-13: 9780134997193, Edition: 11th
Authorized translation from the English language edition, entitled Computer Organization and Architecture, 11th Edition, by William Stallings, published by Pearson Education, Inc, publishing as Pearson, Copyright ? 2019 Pearson Education, Inc.
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc.
Polish language edition published by Polish Scientific Publishers PWN, Copyright ? 2022
Przekład: Piotr Fabijańczyk na zlecenie WITKOM Witold Sikorski
Projekt okładki polskiego wydania: INT-MEDIA
Wydawca: Wioleta Szczygielska-Dybciak
Redaktor prowadzący: Monika Zabrocka-Kutera
Redaktor: Anna Bogdanienko
Koordynator produkcji: Anna Bączkowska
Skład wersji elektronicznej na zlecenie Wydawnictwo Naukowe PWN S.A.: Michał Latusek
Książka, którą nabyłeś, jest dziełem twórcy i wydawcy. Prosimy, abyś przestrzegał praw, jakie im przysługują. Jej zawartość możesz udostępnić nieodpłatnie osobom bliskim lub osobiście znanym. Ale nie publikuj jej w internecie. Jeśli cytujesz jej fragmenty, nie zmieniaj ich treści i koniecznie zaznacz, czyje to dzieło. A kopiując jej część, rób to jedynie na użytek osobisty.
Szanujmy cudzą własność i prawo
Więcej na www.legalnakultura.pl
Polska Izba Książki
Copyright ? for the Polish edition by Wydawnictwo Naukowe PWN S.A.
Warszawa 2022
ISBN: 978-83-01-22641-1
eBook został przygotowany na podstawie wydania papierowego z 2022 r. (Wydanie I)
Warszawa 2024
Wydawnictwo Naukowe PWN SA
02-460 Warszawa, ul. Gottlieba Daimlera 2
tel. 22 69 54 321, faks 22 69 54 288
infolinia 801 33 33 88
e-mail: pwn@pwn.com.pl, reklama@pwn.plwww.pwn.pl
CELE DYDAKTYCZNE
Po przestudiowaniu tego rozdziału powinieneś być w stanie:
- Przedstawić przegląd podstawowych właściwości rozkazów maszynowych.
- Opisać typy argumentów używanych w typowych listach rozkazów maszynowych.
- Przedstawić przegląd typów danych w architekturze x86 i ARM.
- Opisać typy argumentów obsługiwanych przez typowe listy rozkazów maszynowych.
- Przedstawić przegląd typów operacji w architekturze x86 i ARM.
- Zrozumieć różnice pomiędzy grubokońcowością (ang. big endian), cienkokońcowością (ang. little endian) i dwukońcowością (ang. bi-endian).
Licznych komponentów opisanych w tej książce nie widzi użytkownik lub programista komputera. Jeśli programista używa języka wysokiego poziomu, takiego jak Pascal lub Ada, to nie jest w stanie zauważyć zbyt wielu szczegółów architektury wykorzystywanego komputera.
Jednym z obszarów, w których zarówno projektant komputera, jak i jego programista mają wspólny obraz maszyny, jest lista rozkazów maszynowych. Z punktu widzenia projektanta lista rozkazów maszynowych określa wymagania funkcjonalne w stosunku do procesora: implementacja procesora jest zadaniem, które w znacznej mierze polega na wdrożeniu listy rozkazów maszynowych. Jedynie użytkownik, który wybiera programowanie w języku maszynowym (właściwie w języku asemblera, patrz rozdział 15), jest świadomy struktury rejestrów i pamięci, rodzaju danych bezpośrednio obsługiwanych przez maszynę oraz funkcjonowanie ALU.
Opis listy rozkazów maszynowych komputera oznacza duży krok naprzód w wyjaśnianiu działania procesora. W związku z tym koncentrujemy się na instrukcjach maszynowych w tym i następnym rozdziale.
13.1. WŁASNOŚCI ROZKAZÓW MASZYNOWYCH
Działanie procesora określają wykonywane przez niego rozkazy, zwane rozkazami maszynowymi lub rozkazami komputerowymi. Zbiór różnych rozkazów, które procesor może wykonać, jest określany jako lista rozkazów procesora.
Elementy rozkazu maszynowego
Każdy rozkaz musi zawierać informacje wymagane przez procesor do jego wykonania. Rysunek 13.1, który jest powtórzeniem rysunku 3.6 z tomu I, przedstawia etapy związane z wykonaniem rozkazów. W rezultacie etapy te definiują elementy rozkazu maszynowego. Są to następujące elementy:
Rysunek 13.1. Schemat cyklu rozkazu
- Kod operacji: Określa operację do wykonania (np. ADD, we-wy). Operacja jest określona przez kod binarny, znany jako kod operacji lub opcode.
- Odniesienie do argumentów źródłowych: Operacja może obejmować jeden lub więcej argumentów źródłowych. Są to argumenty, które są danymi wejściowymi operacji.
- Odniesienie do wyniku: Operacja może prowadzić do powstania wyniku.
- Odniesienie do następnego rozkazu: Precyzuje procesorowi, skąd ma pobrać następny rozkaz po zakończeniu wykonywania bieżącego rozkazu.
Adres kolejnego rozkazu do pobrania może być adresem rzeczywistym lub adresem wirtualnym, w zależności od architektury systemu. Ogólnie rzecz biorąc, rozróżnienie jest przejrzyste dla architektury listy rozkazów. W większości przypadków następny rozkaz przewidziany do pobrania następuje bezpośrednio po bieżącym rozkazie. W takich przypadkach nie istnieje jawne odniesienie do kolejnego rozkazu. Gdy odwołanie takie jest wymagane, musi być dostarczony adres w pamięci głównej lub w pamięci wirtualnej. Formę, w której jest dostarczany adres, omówimy w rozdziale 14.
Argumenty źródłowe i wyniki mogą się znajdować w jednym z czterech obszarów:
- Pamięć główna lub wirtualna: Podobnie jak w przypadku odwołań do następnego rozkazu, konieczne jest dostarczenie adresu pamięci głównej lub wirtualnej.
- Rejestr procesora: Z rzadkimi wyjątkami procesor zawiera jeden lub więcej rejestrów, do których mogą się odwoływać rozkazy maszynowe. Jeśli istnieje tylko jeden rejestr, to odwołanie do niego może być domyślne. Jeżeli istnieje więcej niż jeden rejestr, to każdemu przypisywana jest unikatowa nazwa lub numer, a rozkaz musi zawierać numer żądanego rejestru.
- Natychmiastowy: Wartość argumentu jest przechowywana w polu wykonywanego rozkazu.
- Urządzenie we-wy: Rozkaz musi określać moduł we-wy i urządzenie używane podczas operacji. Jeśli używane jest we-wy odwzorowane w pamięci, jest to po prostu kolejny adres w pamięci głównej lub wirtualnej.
Reprezentacja rozkazu
W systemach komputerowych każdy rozkaz jest reprezentowany za pomocą ciągu bitów. Rozkaz jest dzielony na pola odpowiadające elementom składowym rozkazu. Prosty przykład formatu rozkazu pokazano na rysunku 13.2. Innym przykładem jest format rozkazu IAS z rysunku 1.7 (tom I). Na większości list rozkazów używany jest więcej niż jeden format. Podczas wykonywania rozkazu jest on wczytywany do rejestru rozkazów (IR) w procesorze. Procesor musi być w stanie wyodrębnić dane z różnych pól rozkazu tak, by wykonać wymaganą operację.
Rysunek 13.2. Prosty format rozkazu
Zarówno programiście, jak i czytelnikowi trudno jest posługiwać się binarną reprezentacją rozkazów maszynowych. Z tego powodu powszechną praktyką stało się używanie symbolicznej reprezentacji rozkazów maszynowych. Przykładem tego jest lista rozkazów IAS z tabeli 1.1 (tom I).
Kody operacji są reprezentowane przez skróty, zwane mnemonikami, które określają operację. Typowe przykłady to:
ADD Dodaj
SUB Odejmij
MUL Pomnóż
DIV Podziel
LOAD Ładuj dane z pamięci
STOR Zapisz dane w pamięci
Argumenty są również reprezentowane symbolicznie. Na przykład rozkaz
ADD R, Y
może oznaczać: dodaj wartość zawartą w pozycji danych Y do zawartości rejestru R. W tym przykładzie Y odnosi się do adresu komórki pamięci, natomiast R określa dany rejestr. Zauważmy, że operacja jest przeprowadzana na zawartościach tych lokacji, a nie na adresach.
Jest więc możliwe napisanie programu w języku maszynowym w postaci symbolicznej. Każdy symboliczny kod operacji ma ustaloną reprezentację binarną, a programista określa lokalizację każdego symbolicznego argumentu. Programista może na przykład rozpocząć od listy definicji:
X = 513
Y = 514
i tak dalej. Prosty program zaakceptowałby te symboliczne dane wejściowe, przekonwertował kody operacji i odwołania do argumentów do postaci binarnej i zbudował binarne instrukcje maszynowe.
Obecnie praktycznie nie spotyka się osób programujących w języku maszynowym. Większość dzisiejszych programów jest tworzona w języku wysokiego poziomu lub, jeśli to się nie uda, w języku asemblera, który jest przedmiotem dyskusji w rozdziale 15. Jednak symboliczny język maszynowy pozostaje użytecznym narzędziem do opisywania rozkazów maszynowych i będziemy go używać do tego celu.
Rodzaje rozkazów
Rozważmy rozkaz w języku wysokiego poziomu, który może być wyrażony w takim języku, jak BASIC czy FORTRAN. Na przykład
X = X + Y
Rozkaz określa dodanie wartości przechowywanej w Y do wartości przechowywanej w X oraz umieszczenie wyniku w X. Jak może to zostać wykonane za pomocą rozkazów maszynowych? Załóżmy, że zmienne X i Y odpowiadają lokacjom 513 i 514. Jeśli przyjmiemy prostą listę rozkazów maszynowych, to operacja ta może być wykonana za pomocą trzech rozkazów:
1. Załaduj do rejestru zawartość komórki pamięci 513.
2. Dodaj zawartość komórki pamięci 514 do rejestru.
3. Przechowaj zawartość rejestru w komórce pamięci 513.
Jak można zauważyć, pojedyncza instrukcja w języku BASIC może wymagać trzech rozkazów maszynowych. Jest to typowa zależność między językiem wysokiego poziomu a językiem maszynowym. W przypadku języka wysokiego poziomu operacje są wyrażane w zwartej formie algebraicznej. Z kolei język maszynowy wyraża operacje w postaci podstawowej, obejmując ruch danych do lub z rejestrów.
Posługując się tym prostym przykładem, rozważmy rodzaje rozkazów, które muszą być przewidziane w rzeczywistym komputerze. Komputer powinien dysponować listą rozkazów, które umożliwiają użytkownikowi formułowanie dowolnych zadań przetwarzania danych. Innym sposobem postrzegania tego problemu jest rozważenie możliwości języka wysokiego poziomu. Dowolny program napisany w języku wysokiego poziomu musi zostać przetłumaczony na język maszynowy tak, by mógł być wykonany. Lista rozkazów maszynowych musi być zatem wystarczająca do wyrażenia dowolnych instrukcji języka wysokiego poziomu. Uwzględniając to, możemy podzielić rozkazy na następujące rodzaje:
- Przetwarzanie danych: Rozkazy arytmetyczne i logiczne.
- Przechowywanie danych: Rozkazy dotyczące pamięci.
- Przesyłanie danych: Rozkazy we-wy.
- Sterowanie: Rozkazy testowania i rozgałęzienia.
Rozkazy arytmetyczne zapewniają zdolność obliczeniową przetwarzania danych numerycznych. Rozkazy logiczne (Boole'a) dotyczą operacji wykonywanych na bitach słowa, które są traktowane jako bity, a nie liczby. Wobec tego umożliwiają przetwarzanie dowolnych innych rodzajów danych, które użytkownik może chcieć wykorzystywać. Operacje te są wykonywane głównie na danych znajdujących się w rejestrach procesora. Wobec tego muszą istnieć rozkazy dotyczące pamięci służące do przenoszenia danych między pamięcią a rejestrami. Rozkazy we-wy są konieczne do przenoszenia danych i programów do pamięci oraz wyników obliczeń do użytkownika. Rozkazy testowania są używane do sprawdzania wartości słów danych lub stanu obliczeń. Rozkazy rozgałęzienia są wykorzystywane do przeskakiwania do innego zestawu rozkazów zależnie od podjętych decyzji.
Przeanalizujemy różne rodzaje rozkazów bardziej szczegółowo w dalszym ciągu rozdziału.
Liczba adresów
Jeden z tradycyjnych sposobów opisywania architektury procesora bazuje na liczbie adresów zawartych w każdym rozkazie. Wymiar ten stracił na znaczeniu wraz ze wzrostem złożoności procesora. Przeanalizowanie tej własności może być jednak użyteczne.
Jaka jest maksymalna liczba adresów, która może być potrzebna w rozkazie? Jest oczywiste, że operacje arytmetyczne i logiczne wymagają argumentów. Praktycznie wszystkie operacje arytmetyczne i logiczne są operacjami jednoargumentowymi, albo dwuargumentowymi. Wobec tego wymagane są maksymalnie dwa adresy będące odniesieniem do argumentów. Wynik operacji musi być zapisany, zatem wymagany jest również trzeci adres. Na zakończenie, po wykonaniu rozkazu, musi być pobrany kolejny rozkaz, do czego potrzebny jest jego adres.
Z powyższego rozumowania wynika, że rozkaz powinien zawierać cztery odniesienia adresowe: dwa dotyczące argumentów, jedno wyniku i jedno kolejnego rozkazu. W praktyce czteroadresowe rozkazy występują bardzo rzadko. Większość procesorów to jednostki jedno-, dwu- lub trójadresowe, przy czym adres kolejnego rozkazu jest domyślny i uzyskiwany z licznika programu. Większość architektur ma również kilka rozkazów specjalnego przeznaczenia z większą liczbą argumentów. Na przykład liczne rozkazy dotyczące ładowania i przechowywania w architekturze ARM, opisanej w rozdziale 14, wskazują do 17 rejestrów argumentów w obrębie jednego rozkazu.
Na rysunku 13.3 zamieszczono porównanie typowych rozkazów jedno-, dwu- i trójadresowych, które mogą być wykorzystane do obliczenia Y = (A - B)/[C + (D × E].
Za pomocą trzech adresów każdy rozkaz ustala dwie lokalizacje argumentów i jedną lokalizację wyniku. Ponieważ zmiana lokalizacji argumentów nie jest pożądana, do przechowywania wyników pośrednich używana jest tymczasowa lokalizacja T. Zauważmy, że występują tu cztery rozkazy, natomiast pierwotne wyrażenie ma pięć argumentów.
W związku z koniecznością objęcia aż trzech odniesień adresowych trójadresowe formaty rozkazów są stosunkowo długie i z tego powodu nie są powszechnie stosowane. W przypadku rozkazów dwuadresowych oraz operacji binarnych jeden adres musi być używany podwójnie, jednocześnie jako adres argumentu i wyniku. Wobec tego rozkaz SUB Y, B powoduje wykonanie obliczenia Y - B oraz przechowania wyniku w zmiennej Y. Stosowanie dwuadresowego formatu umożliwia zmniejszenie wymagań dotyczących pojemności, aczkolwiek wynikają z tego pewne zawiłości. By zapobiec zmianie wartości argumentu, stosowany jest rozkaz MOVE, powodujący przeniesienie jednej z wartości do lokalizacji wyniku lub lokalizacji tymczasowej przed wykonaniem operacji. Zakres naszego analizowanego programu rozszerza się zatem do sześciu rozkazów.
Jeszcze prostszym formatem jest rozkaz jednoadresowy. Jego działanie jest możliwe, jeśli drugi adres jest adresem domyślnym. Format ten był powszechny we wcześniejszych komputerach, w których adres domyślny dotyczył rejestru procesora
Rysunek 13.3. Programy do wykonania
zwanego akumulatorem (AC). Akumulator zawiera jeden z argumentów i jest używany do przechowywania wyniku. W rezultacie analizowany program rozrasta się do ośmiu rozkazów.
W rzeczywistości jest możliwe, by niektóre rozkazy funkcjonowały bez adresu. Rozkazy bezadresowe mogą być stosowane w przypadku szczególnej organizacji pamięci zwanej stosem. Stos jest zbiorem lokacji funkcjonującym na zasadzie ostatni na wejściu - pierwszy na wyjściu. Stos znajduje się w znanym położeniu i często przynajmniej jego dwa szczytowe elementy są przechowywane w rejestrach procesora. Stosy opisano w dodatku E. Ich użycie omówiono w dalszej części tego rozdziału oraz w rozdziale 14.
Tabela 13.1 zawiera podsumowanie interpretacji rozkazów, niezawierających w ogóle lub zawierających jeden, dwa lub trzy adresy. W każdym przypadku jest przyjęte, że adres kolejnego rozkazu jest domyślny, natomiast jedna operacja wymaga dwóch argumentów i jej rezultatem jest pojedynczy wynik.
Tabela 13.1. Wykorzystanie adresów rozkazu (rozkazy nierozgałęziające)
Liczba adresów
Reprezentacja symboliczna
Interpretacja
3
OP A, B, C
A ? B OP C
2
OP A, B
A ? A OP B
1
OP A
AC ? AC OP A
0
OP
T ? (T - 1) OP T
AC = akumulator,
T = wierzchołek stosu,
(T - 1) = drugi element na stosie,
A, B, C = lokacje w pamięci lub rejestrze.
Liczba adresów w rozkazie jest podstawową kwestią projektową. Mniejsza liczba adresów w rozkazie oznacza prymitywniejsze rozkazy, co z kolei wymaga stosowania prostszych procesorów. Oznacza to także krótsze rozkazy. Jednak programy zawierają wówczas więcej rozkazów, co na ogół powoduje wydłużenie czasów wykonywania oraz wydłużenie i skomplikowanie samych programów. Między rozkazami jedno- i wieloadresowymi występuje pewien próg. W przypadku rozkazów jednoadresowych programista ma na ogół dostęp tylko do jednego rejestru ogólnego przeznaczenia, czyli do akumulatora. W przypadku rozkazów wieloadresowych zazwyczaj występuje wiele rejestrów ogólnego przeznaczenia. W rezultacie niektóre operacje mogą być wykonywane na samych tylko rejestrach. Ze względu na to, że dostęp do rejestrów jest szybszy niż do pamięci, przyspiesza to wykonywanie programów. Z powodu elastyczności i możliwości używania wielu rejestrów w większości współczesnych systemów komputerowych stosuje się zarówno rozkazy dwu-, jak i trójadresowe.
Zagadnienia projektowe dotyczące wyboru liczby adresów w rozkazie dodatkowo komplikują inne czynniki. Istnieje problem, czy adres odnosi się do lokacji w pamięci, czy do rejestru. Ponieważ rejestrów jest mniej, to odniesienie do rejestru wymaga mniejszej liczby bitów. Ponadto, jak zobaczymy w rozdziale 14, system komputerowy może obsługiwać różne tryby adresowania, a określenie specyfikacji tego trybu wymaga użycia jednego lub wielu z tych bitów. W rezultacie w większości projektów procesorów przewidziano różne formaty rozkazów.
Projektowanie listy rozkazów
Jednym z najbardziej interesujących i najczęściej analizowanych aspektów projektowania systemu komputerowego jest projektowanie jego listy rozkazów. Jest to bardzo złożone zagadnienie, ponieważ wpływa na wiele aspektów systemu. Lista rozkazów definiuje liczne funkcje realizowane przez procesor i z tego powodu ma znaczący wpływ na implementację procesora. Lista rozkazów jest środkiem, za pomocą którego programista steruje procesorem. Z tego powodu też podczas projektowania listy rozkazów muszą być uwzględniane wymagania programisty.
Zaskakujące może być to, że pewne najbardziej podstawowe problemy związane z projektowaniem list rozkazów nadal są przedmiotem debat. Istotnie, w ostatnich latach poziom niezgodności dotyczący tych podstawowych problemów projektowych wzrósł. Do najważniejszych należą:
- Repertuar operacji. Ile operacji, które operacje i jak złożone operacje powinny być przewidziane.
- Rodzaje danych. Różne rodzaje danych, na których wykonywane są operacje.
- Format rozkazu. Długość rozkazu w bitach, liczba adresów, rozmiar różnych pól itd.
- Rejestry. Liczba rejestrów w procesorze, do których mogą się odnosić rozkazy, oraz ich zastosowanie.
- Adresowanie. Tryb lub tryby, w których są specyfikowane adresy argumentów.
Zagadnienia te są ze sobą ściśle powiązane i muszą być rozważane łącznie podczas projektowania listy rozkazów. Oczywiście w tej książce muszą być one rozpatrzone po kolei, jednak spróbujemy pokazać ich wzajemne zależności.
Ze względu na wagę tego tematu większość części trzeciej poświęcona jest projektowaniu listy rozkazów. Po części przeglądowej, w dalszej części tego rozdziału omawiamy typy danych i repertuar operacji. W rozdziale 14 omówimy tryby adresowania (z uwzględnieniem rejestrów) oraz formaty rozkazów. Z kolei w rozdziale 17 dyskutujemy na temat komputera z ograniczoną listą rozkazów (RISC). Architektura RISC poddaje w wątpliwość wiele decyzji dotyczących projektowania listy rozkazów podejmowanych tradycyjnie w przypadku komputerów komercyjnych.
13.2. RODZAJE ARGUMENTÓW
Rozkazy maszynowe operują na danych. Najważniejsze ogólne kategorie danych to:
- adresy,
- liczby,
- znaki,
- dane logiczne.
Omawiając tryby adresowania w rozdziale 14, zobaczymy, że w rzeczywistości adresy są formą danych. W wielu przypadkach muszą być wykonywane pewne obliczenia na odniesieniach w rozkazach w celu wyznaczenia adresu w pamięci głównej lub wirtualnej. W takim ujęciu adresy są traktowane jak liczby całkowite bez znaku.
Innymi powszechnymi rodzajami danych są liczby, znaki i dane logiczne. Każdy z nich zostanie krótko przeanalizowany w dalszej części tego podrozdziału. Ponadto w niektórych komputerach występują wyspecjalizowane rodzaje danych lub struktur danych. Na przykład niektórzy użytkownicy komputerów mogą działać bezpośrednio na listach lub ciągach znaków.
Liczby
Wszystkie języki maszynowe obejmują dane numeryczne. Nawet w przypadku przetwarzania danych nienumerycznych liczby są wykorzystywane w licznikach, do określania szerokości pól itd. Istotną różnicą między liczbami używanymi w zwykłej matematyce a liczbami przechowywanymi w komputerze jest to, że te ostatnie są ograniczone. Jest to prawda w podwójnym sensie. Po pierwsze, istnieje granica wielkości liczb reprezentowalnych w komputerze, a po drugie, w przypadku liczb zmiennoprzecinkowych - granica ich dokładności. Programista powinien zatem być świadomy konsekwencji zaokrąglania, przepełnienia i niedomiaru.
W komputerach powszechne są trzy rodzaje danych numerycznych:
- całkowite lub stałoprzecinkowe,
- zmiennoprzecinkowe,
- dziesiętne.
Dwa pierwsze rodzaje omówiliśmy już dość szczegółowo w rozdziale 10 (tom I). Pozostaje zatem pokrótce omówić liczby dziesiętne.
Pomimo że wszystkie wewnętrzne operacje komputera są z natury binarne, to użytkownicy systemu operują liczbami dziesiętnymi. Istnieje zatem konieczność konwersji liczb dziesiętnych na binarne na wejściu oraz binarnych na dziesiętne na wyjściu. W przypadku zastosowań, w których występuje wiele operacji we-wy oraz stosunkowo niewielka liczba prostych obliczeń, wygodniej jest przechowywać i przetwarzać liczby w postaci dziesiętnej. Najbardziej powszechną reprezentacją służącą do tego celu są upakowane liczby dziesiętne.[1]
W przypadku upakowanych liczb dziesiętnych każda cyfra dziesiętna jest reprezentowana w 4-bitowym kodzie tworzonym w oczywisty sposób. Wobec tego 0 = 0000, 1 = 0001, ... , 8 = 1000 i 9 = 1001. Zauważmy, że jest to kod raczej nieefektywny, ponieważ używa tylko 10 spośród możliwych 16 wartości 4-bitowych. W celu utworzenia liczb 4-bitowe kody są zestawiane szeregowo, zwykle w wielokrotnościach 8 bitów. Kodem liczby 246 jest zatem 0000 0010 0100 0110. Kod ten jest oczywiście mniej zwarty niż prosta reprezentacja binarna, jednak pozwala uniknąć konwersji. Liczby ujemne mogą być reprezentowane przez dołączenie 4-bitowej cyfry znaku albo z lewej, albo z prawej strony ciągu upakowanych cyfr dziesiętnych. Standardowe wartości znaków to 1100 dla liczb dodatnich (+) i 1101 dla liczb ujemnych (-).
W wielu komputerach przewidziano rozkazy arytmetyczne do wykonywania operacji bezpośrednio na upakowanych liczbach dziesiętnych. Algorytmy te są bardzo podobne do tych opisanych w podrozdziale 10.3 (tom I), muszą jednak uwzględniać operację przeniesienia dziesiętnego.
Znaki
Powszechną formą danych jest tekst lub ciągi znaków. Chociaż dane tekstowe są najwygodniejsze dla ludzi, to nie mogą one być bezpośrednio i łatwo przechowywane oraz przesyłane przez systemy przetwarzania i komunikacji. Systemy te zostały bowiem zaprojektowane z myślą o danych binarnych. W związku z tym opracowano wiele kodów, za pomocą których znaki są reprezentowane w postaci ciągów bitów. Być może najwcześniejszym, powszechnie znanym przykładem jest kod Morse'a. Obecnie najczęściej używanym kodem znaków jest międzynarodowy alfabet referencyjny (IRA), określany w Stanach Zjednoczonych jako American Standard Code for Information Interchange (ASCII, zobacz dodatek D). Każdy znak w tym kodzie jest reprezentowany przez unikatowy wzór 7-bitowy, zatem możliwe jest reprezentowanie 128 różnych znaków. Jest to liczba większa niż wymagana do reprezentowania znaków drukowanych, dlatego część wzorów odpowiada za znaki sterowania. Niektóre z nich są stosowane do sterowania drukowaniem znaków na stronie, inne są wykorzystywane w procedurach komunikacyjnych. Znaki zakodowane za pomocą ASCII są praktycznie zawsze przechowywane i transmitowane przy użyciu 8 bitów na znak. Wartość ósmego bitu może zostać ustawiona jako 0 lub jako bit parzystości do wykrywania błędów. W tym ostatnim przypadku bit jest ustalany w ten sposób, że całkowita liczba binarnych jedynek w każdym oktecie jest zawsze nieparzysta (parzystość nieparzysta) lub zawsze parzysta (parzystość parzysta).
Zauważmy, że w tabeli D.1 (dodatek D) dla wzoru bitowego 011XXXX cyfry od 0 do 9 są reprezentowane przez ich binarne równoważniki, 0000 do 1001, znajdujące się w prawej części wzoru. Jest to ten sam kod co stosowany w przypadku upakowanych liczb dziesiętnych. Ułatwia to konwersję między 7-bitowym kodem ASCII a 4-bitową upakowaną reprezentacją dziesiętną.
Innym kodem używanym do kodowania znaków jest EBCDIC (ang. Extended Binary Coded Decimal Interchange Code). EBCDIC jest stosowany w maszynach mainframes IBM. Jest to kod 8-bitowy. Podobnie jak ASCII, EBCDIC jest kompatybilny z upakowaną reprezentacją dziesiętną. W przypadku EBCDIC kody od 11110000 do 11111001 reprezentują cyfry od 0 do 9.
Dane logiczne
Zwykle każde słowo lub inna jednostka adresowalna (bajt, półsłowo itd.) jest traktowane jako jednostka danych. Jest jednak czasem użyteczne rozpatrywanie jednostki n-bitowej jako składającej się z n jednobitowych pozycji, z których każda ma wartość 1 lub 0. Dane widziane w ten sposób są traktowane jako dane logiczne.
Widok zorientowany na bity ma dwie zalety. Po pierwsze, czasami możemy chcieć przechowywać tablicę elementów boolowskich lub binarnych, w której każdy element może przyjmować tylko wartości 1 (prawda) i 0 (fałsz). Dane w postaci logicznej mogą być bardziej efektywnie przechowywane w pamięci. Po drugie, występują sytuacje, w których chcemy manipulować bitami jednostki danych. Na przykład, jeśli operacje zmiennoprzecinkowe są zaimplementowane w oprogramowaniu, to w pewnych operacjach zachodzi potrzeba przesuwania znaczących bitów. Inny przykładem jest konwersja z IRA na upakowany dziesiętny. W tym przypadku konieczne jest wyodrębnienie 4 prawych bitów z każdego bajta.
Zauważmy, że w poprzednich przykładach te same dane są czasami traktowane jako logiczne, a innym razem jako liczbowe lub tekstowe. "Rodzaj" jednostki danych zależy od wykonywanej na niej operacji. Chociaż zwykle nie występuje to w językach wysokiego poziomu, prawie zawsze zdarza się w przypadku języka maszynowego.
13.3. TYPY DANYCH W ARCHITEKTURACHINTEL X86 I ARM
Rodzaje danych w architekturze x86
Architektura x86 może obsługiwać typy danych o długości 8 (bajt), 16 (słowo), 32 (podwójne słowo), 64 (poczwórne słowo) i 128 (podwójne poczwórne słowo). By zapewnić maksymalną elastyczność struktur danych i efektywne wykorzystanie pamięci, słowa nie muszą być wyrównywane pod adresami numerowanymi parzyście. Podwójne słowa nie muszą być wyrównywane pod adresami podzielnymi przez 4, natomiast słowa poczwórne nie muszą być wyrównywane pod adresami podzielnymi przez 8 i tak dalej. Jednakże, gdy dostęp do danych odbywa się przez 32-bitową magistralę, wtedy transfery danych odbywają się w podwójnych słowach jako jednostkach, zaczynając od adresów podzielnych przez 4. Procesor przekształca żądanie odnoszące się do niewyrównanych wartości w sekwencję żądań dostosowanych do transferu magistrali. Podobnie jak w przypadku wszystkich maszyn Intel 80x86, x86 używa stylu określanego jako cienkokońcowy (ang. little-endian). Oznacza to, że najmniej znaczący bajt jest przechowywany pod najniższym adresem (patrz dodatek 13A, w którym omówiono cienkokońcowość).
Bajt, słowo, słowo podwójne, słowo poczwórne i podwójne słowo poczwórne są określane jako ogólne rodzaje danych. Ponadto x86 obsługuje imponującą gamę określonych rodzajów danych, które są rozpoznawane i obsługiwane przez określone rozkazy. Ich podsumowanie zamieszczono w tabeli 13.2.
Tabela 13.2. Rodzaje danych x86
Rodzaj danych
Opis
Ogólne
Lokacje bajta, słowa (16 bitów), podwójnego słowa (32 bity), poczwórnego słowa (64 bity) i podwójnego słowa poczwórnego (128 bitów) z dowolną zawartością binarną.
Całkowite
Wartości binarne ze znakiem, zawarte w bajcie, słowie lub podwójnym słowie, w reprezentacji uzupełnienia do dwóch.
Porządkowe
Liczby całkowite bez znaku zawarte w bajcie, słowie lub słowie podwójnym.
Nieupakowana binarna reprezentacja liczb dziesiętnych (BCD)
Reprezentacja cyfry BCD z zakresu od 0 do 9, o jednej cyfrze w każdym bajcie.
Upakowana BCD
Upakowana bajtowa reprezentacja dwóch cyfr BCD, wartości z zakresu od 0 do 99.
Wskaźnik bliski
16-, 32- lub 64-bitowy adres efektywny, reprezentujący przesunięcie wewnątrz segmentu. Używany dla wszystkich wskaźników w pamięci niesegmentowanej oraz do odniesień wewnątrz segmentu w pamięci segmentowanej.
Wskaźnik daleki
Adres logiczny składający się z 16-bitowego selektora segmentu i 16-, 32- lub 64-bitowego przesunięcia. Używane do odwołań w pamięci segmentowanej, w której segment, do którego uzyskiwany jest dostęp, musi być wyraźnie określony.
Pole bitowe
Sekwencja sąsiadujących bitów, w której pozycja każdego bitu jest traktowana jako jednostka niezależna. Łańcuch bitów może się rozpoczynać od dowolnej pozycji bitowej dowolnego bajta i może zawierać do 32 bitów.
Łańcuch bitów
Ciągła sekwencja bitów zawierająca od zera do 223 - 1 bitów.
Łańcuch bajtów
Sekwencja sąsiadujących bajtów, słów lub słów podwójnych zawierająca od zera do 223 - 1 bitów.
Zmiennoprzecinkowe
Zobacz rysunek 13.4
Upakowane SIMD (single instruction, multiple data)
Upakowane 64-bitowe i 128-bitowe rodzaje danych.
Numeryczne typy danych x86 przedstawiono na rysunku 13.4. Liczby całkowite ze znakiem są reprezentowane w notacji dopełnienia do dwóch i mogą mieć długość 16, 32 lub 64 bitów. Rodzaj zmiennoprzecinkowy w rzeczywistości odnosi się do zbioru rodzajów, które są używane przez jednostkę zmiennoprzecinkową i obsługiwane przez rozkazy zmiennoprzecinkowe. Reprezentacje zmiennoprzecinkowe są zgodne ze standardem IEEE 754.
Upakowane (wektorowe) rodzaje danych SIMD (ang. single-instruction-multiple-data) zostały wprowadzone do architektury x86 jako część rozszerzeń listy rozkazów w celu optymalizacji wydajności aplikacji multimedialnych. Rozszerzenia te obejmują instrukcje MMX (rozszerzenia multimedialne) i SSE (rozszerzenia strumieniowe SIMD). Podstawowa koncepcja polega na tym, że wiele argumentów jest pakowanych do pojedynczego elementu pamięci, do którego następuje odwołanie, i wiele argumentów jest przetwarzanych równolegle. Rodzaje danych są następujące:
Rysunek 13.4. Numeryczne formaty danych x86
- Upakowany bajt i upakowany bajt całkowity: Bajty upakowane w 64-bitowe słowo poczwórne lub 128-bitowe podwójne słowo poczwórne, interpretowane jako pole bitowe lub jako liczba całkowita.
- Upakowane słowo i upakowane słowo całkowite: 16-bitowe słowa upakowane w 64-bitowe słowo poczwórne lub 128-bitowe podwójne słowo poczwórne, interpretowane jako pole bitowe lub jako liczba całkowita.
- Upakowane słowo podwójne i upakowane słowo podwójne całkowite: 32-bitowe podwójne słowa upakowane w 64-bitowe podwójne słowo lub 128-bitowe podwójne słowo poczwórne, interpretowane jako pole bitowe lub jako liczba całkowita.
- Upakowane słowo poczwórne i upakowane słowo poczwórne całkowite: Dwa 64-bitowe słowa poczwórne spakowane w 128-bitowe podwójne słowo poczwórne, interpretowane jako pole bitowe lub jako liczba całkowita.
- Upakowane wartości zmiennoprzecinkowe o pojedynczej precyzji i upakowane wartości zmiennoprzecinkowe o podwójnej precyzji: Cztery 32-bitowe wartości zmiennoprzecinkowe lub dwie 64-bitowe wartości zmiennoprzecinkowe spakowane w 128-bitowy podwójne słowo poczwórne.
Rodzaje danych w architekturze ARM
Procesory ARM obsługują typy danych o długości 8 bitów (1 bajt), 16 bitów (pół słowa) i 32 bitów (słowo). Zwykle dostęp do półsłów powinien być wyrównany do półsłów, a dostęp do słów powinien być wyrównany do słów. W przypadku niedopasowanych prób dostępu architektura obsługuje trzy opcje:
- Przypadek domyślny:
- Adres jest traktowany jako obcięty, przy czym bity [1:0] adresu są traktowane jako zero dla dostępu do słowa, a bit [0] adresu jako zero w przypadku dostępu do półsłowa.
- Rozkazy ARM dotyczące ładowania pojedynczego słowa zostały zdefiniowane w architekturze tak, by przesuwać w prawo dane wyrównane do słowa przesyłane przez adres niewyrównany do słowa o jeden, dwa lub trzy bajty, w zależności od wartości dwóch najmniej znaczących bitów adresu.
- Sprawdzanie wyrównania: Gdy ustawiony jest odpowiedni bit kontrolny, wtedy przy próbie niewyrównanego dostępu sygnał przerwania danych wskazuje błąd wyrównania.
- Dostęp niewyrównany: Gdy ta opcja jest włączona, procesor wykorzystuje jeden lub więcej dostępów do pamięci w celu wygenerowania wymaganego transferu sąsiednich bajtów w sposób niewidoczny dla programisty.
Dla wszystkich trzech typów danych (bajt, półsłowo i słowo) obsługiwana jest interpretacja bezznakowa, w której wartość reprezentuje nieujemną liczbę całkowitą bez znaku. Wszystkie trzy typy danych mogą być również używane w przypadku liczb całkowitych ze znakiem w notacji uzupełnienia do dwóch.
Większość implementacji procesorów ARM nie zapewnia sprzętowej obsługi zmiennoprzecinkowej, w wyniku czego oszczędzana jest energia i powierzchnia. W przypadku gdy w takich procesorach wymagana jest obsługa arytmetyki zmiennoprzecinkowej, konieczne jest zaimplementowanie tego w oprogramowaniu. ARM obsługuje opcjonalny koprocesor zmiennoprzecinkowy, który obsługuje zmiennoprzecinkowe rodzaje danych o pojedynczej i podwójnej precyzji, zdefiniowane w standardzie IEEE 754.
OBSŁUGA KOLEJNOŚCI BAJTÓW. Bit stanu (E-bit) w rejestrze sterowania systemu jest ustawiany i kasowany programowo za pomocą rozkazu SETEND. E-bit określa, który tryb kolejności bajtów jest używany do ładowania i przechowywania danych. Na rysunku 13.5 przedstawiono funkcjonalność związaną z E-bitem w przypadku operacji ładowania lub przechowywania słowa. Mechanizm ten umożliwia wydajne i dynamiczne ładowanie/przechowywanie danych w przypadku systemów, w których wymagane jest uzyskiwanie dostępu do struktur danych w kierunku przeciwnym do kolejności bajtów używanej przez system operacyjny/środowisko. Zauważmy, że adres każdego bajta danych jest ustalony w pamięci. Pomimo to ścieżka bajtów w rejestrze jest inna.
Rysunek 13.5. Obsługa kolejności bajtów w ARM - ładowanie tekstu/przechowywanie za pomocą E-bitu
13.4. RODZAJE OPERACJI
Liczba kodów operacji różni się znacznie w zależności od procesora. Pomimo to we wszystkich można znaleźć takie same ogólne rodzaje operacji. Użyteczny, typowy podział wygląda następująco:
- operacje transferu danych,
- operacje arytmetyczne,
- operacje logiczne,
- operacje konwersji,
- operacje we-wy,
- operacje sterowania systemowego,
- operacje przekazywania sterowania.
W tabeli 13.3 przedstawiono zestawienie powszechnie stosowanych rodzajów rozkazów należących do wszystkich wymienionych kategorii. W tej części dokonamy krótkiego przeglądu różnych rodzajów operacji, omawiając jednocześnie działania podejmowane przez procesor w celu wykonania poszczególnych rodzajów operacji (podsumowane w tabeli 13.4). To drugie zagadnienie jest tematem dyskusji w rozdziale 16.
Tabela 13.3. Operacje powszechnie występujące w listach rozkazów w architekturze x86
(a) Transfer danych
Nazwa operacji
Opis
MOV Dest, Source
Przenosi dane między rejestrami lub między rejestrem a pamięcią lub natychmiast do rejestru.
XCHG Op1, Op2
Wymienia zawartość między dwoma rejestrami lub między rejestrem i pamięcią.
PUSH Source
Zmniejsza wskaźnik stosu (rejestr ESP), a następnie kopiuje argument źródłowy na szczyt stosu.
POP Dest
Kopiuje wierzchołek stosu do miejsca docelowego i zwiększa ESP.
(b) Arytmetyczne
ADD Dest, Source
Dodaje miejsce docelowe i argument źródłowy oraz przechowuje wynik w miejscu docelowym. Miejscem docelowym może być rejestr lub pamięć. Źródłem może być rejestr, pamięć lub wartości natychmiastowe.
SUB Dest, Source
Odejmuje źródło od miejsca docelowego i przechowuje wynik w miejscu docelowym.
MUL Op
Mnożenie liczby całkowitej bez znaku, argumentu i wartości rejestru AL, AX lub EAX oraz zapisanie w rejestrze. Kod operacji wskazuje rozmiar rejestru.
IMUL Op
Mnożenie całkowite ze znakiem.
DIV Op
Dzielenie bez znaku wartości z rejestrów AX, DX:AX, EDX:EAX lub RDX:RAX (dzielna) przez argument źródłowy (dzielnik) i przechowuje wynik w rejestrach AX (AH:AL), DX:AX, EDX:EAX lub RDX:RAX.
IDIV Op
Dzielenie całkowite ze znakiem.
INC Op
Dodaje 1 do argumentu docelowego, zachowując stan znacznika CF.
DEC Op
Odejmuje 1 od argumentu docelowego, zachowując stan znacznika CF.
NEG Op
Zamienia wartość argumentu na przeciwną
(0 - argument), używając reprezentacji uzupełnienia do dwóch.
CMP Op1, Op2
Porównuje dwa argumenty, odejmując drugi argument od pierwszego i ustawia znacznik stanu w rejestrze EFLAGS zgodnie z wynikami.
(c) Przesuwanie i obracanie
SAL Op, Quantity
Przesuwa argument źródłowy w lewo o od 1 do 31 pozycji bitowych. Puste pozycje bitowe są kasowane. Znacznik CF jest ładowany po usunięciu ostatniego bitu z argumentu.
SAR Op, Quantity
Przesuwa argument źródłowy w prawo o od 1 do 31 pozycji bitowych. Puste pozycje bitowe są czyszczone, jeśli argument jest dodatni i ustawiane, jeśli argument jest ujemny. Znacznik CF jest ładowany po usunięciu ostatniego bitu z argumentu.
SHR Op, Quantity
Przesuwa argument źródłowy w prawo o od 1 do 31 pozycji bitowych. Puste pozycje bitów są czyszczone, a znacznik CF jest ładowany po usunięciu ostatniego bitu z argumentu.
ROL
Op, Quantity
Obraca bity w lewo, z zawijaniem. Znacznik CF jest ładowany po usunięciu ostatniego bitu z argumentu.
ROR Op, Quantity
Obraca bity w prawo, z zawijaniem. Znacznik CF jest ładowany po usunięciu ostatniego bitu z argumentu.
RCL Op, Quantity
Obraca bity, w tym znacznik CF, w lewo z zawijaniem. Ten rozkaz traktuje znacznik CF jako jednobitowe rozszerzenie na górnym końcu argumentu.
RCR Op, Quantity
Obraca bity, w tym flagę CF, w prawo z zawijaniem. Ten rozkaz traktuje znacznik CF jako jednobitowe rozszerzenie na dolnym końcu argumentu.
(d) Logiczne
NOT Op
Odwraca każdy bit argumentu.
AND Dest, Source
Wykonuje operację AND bit po bicie na argumencie docelowym i źródłowym oraz przechowuje wynik w argumencie docelowym.
OR Dest, Source
Wykonuje operację OR bit po bicie na argumencie docelowym i źródłowym oraz przechowuje wynik w argumencie docelowym.
XOR Dest, Source
Wykonuje operację XOR bit po bicie na argumencie docelowym i źródłowym oraz przechowuje wynik w argumencie docelowym.
TEST Op1, Op2
Wykonuje operację AND bit po bicie na dwóch argumentach i ustawia znaczniki stanu S, Z i P. Argumenty pozostają niezmienione.
(e) Przekazywanie sterowania
CALL proc
Zapisuje procedurę łączącą informacje na stosie i gałęziach z wywołaną procedurą określoną za pomocą argumentu. Argument określa adres pierwszego rozkazu w wywołanej procedurze.
RET
Przenosi kontrolę programu na adres zwrotny znajdujący się na wierzchołku stosu. Następuje powrót do rozkazu następującego po rozkazie CALL.
JMP Dest
Przenosi sterowanie programu do innego punktu w strumieniu rozkazów bez rejestrowania informacji zwrotnej. Argument określa adres rozkazu, do którego następuje skok.
Jcc Dest
Sprawdza stan jednego lub większej liczby znaczników stanu w rejestrze EFLAGS (CF, OF, PF, SF i ZF) oraz, jeśli znaczniki są w określonym stanie (warunku), wykonuje skok do rozkazu docelowego określonego przez argument docelowy. Zobacz tabele 13.8 i 13.9.
NOP
Rozkaz nie wykonuje żadnej operacji. Jest to jednobajtowy lub wielobajtowy NOP, który zajmuje miejsce w strumieniu rozkazów, ale nie wpływa na kontekst maszyny, z wyjątkiem rejestru EIP.
HLT
Zatrzymuje wykonywanie rozkazu i wprowadza procesor w stan wstrzymania (HALT). Wznowienie wykonywania następuje po włączeniu przerwania, w efekcie wyjątku debugowania lub po sygnałach BINIT#, INIT# lub RESET#.
WAIT
Powoduje, że procesor wielokrotnie sprawdza i obsługuje oczekujące, niemaskowane wyjątki zmiennoprzecinkowe przed kontynuowaniem.
INT Nr
Przerywa bieżący program i uruchamia określony program przerwań.
(f) We-wy
IN Dest, Source
Kopiuje dane z portu we-wy określonego przez argument źródłowy do argumentu docelowego, który jest lokacją w rejestrze.
INS Dest, Source
Kopiuje dane z portu we-wy określonego przez argument źródłowy do argumentu docelowego, którym jest lokacja w pamięci.
OUT Dest, Source
Kopiuje wartość bajta, słowa lub podwójnego słowa z rejestru źródłowego do portu we-wy określonego przez argument docelowy.
OUTS Dest, Source
Kopiuje bajt, słowo lub podwójne słowo z argumentu źródłowego do portu we-wy określonego w argumencie docelowym. Argument źródłowy to lokacja w pamięci.
Tabela 13.4. Działania wykonywane przez procesor w przypadku różnych rodzajów operacji
Transfer danych
Przeniesienie danych z jednego miejsca do drugiego.
Jeśli zaangażowana jest pamięć:
określenie adresu pamięci, przekształcenie adresu wirtualnego na rzeczywisty, sprawdzenie pamięci podręcznej, zainicjowanie operacji zapisu/odczytu pamięci.
Arytmetyczne
Mogą inicjować transfer danych przed i/lub po operacji.
Wykonanie funkcji w ALU.
Ustawianie kodu warunkowego i znacznika stanu.
Logiczne
Jak w przypadku operacji arytmetycznych.
Konwersja
Podobnie do arytmetycznych i logicznych. Mogą obejmować specjalne funkcje logiczne w celu dokonania konwersji.
Transfer sterowania
Aktualizacja licznika programu. W przypadku wywołania/powrotu z podprogramu zarządzanie przenoszeniem i powiązaniem parametrów.
We-wy
Wydanie rozkazu modułowi we-wy.
Jeśli we-wy jest odwzorowane w pamięci, to określenie adresu odwzorowanego w pamięci.
Transfer danych
Najbardziej podstawowym rodzajem rozkazów maszynowych jest rozkaz transferu danych. Rozkaz taki musi precyzować kilka rzeczy. Po pierwsze, konieczne jest ustalenie położenia argumentów źródłowych i wyników. Może to obejmować pamięć, rejestr lub wierzchołek stosu. Po drugie, konieczne jest określenie długości danych przeznaczonych do transferu. Po trzecie, tak jak w przypadku wszystkich rozkazów z argumentami, konieczne jest określenie trybu adresowania każdego argumentu. To ostatnie zagadnienie jest przedmiotem dyskusji w rozdziale 14.
Wybór rozkazów transferu danych, które mają zostać umieszczone na liście rozkazów, jest przykładem wyborów, jakich musi dokonywać projektant. Na przykład ogólne położenie (pamięć lub rejestr) argumentu może być określone w specyfikacji kodu operacji albo samego argumentu. W tabeli 13.5 zamieszczono przykłady najbardziej typowych rozkazów transferu danych w IBM EAS/390. Zauważmy, że występują tutaj różne warianty ilości danych przeznaczonych do przesłania (8, 16, 32 i 64 bity). Istnieją też różne rozkazy dotyczące transferów z rejestru do rejestru, z rejestru do pamięci i z pamięci do rejestru. Dla porównania, w maszynie VAX istnieje rozkaz przeniesienia (MOV) z wariantami dotyczącymi różnych ilości danych, jednak określa on, czy argument jest w rejestrze, czy w pamięci. Podejście stosowane w komputerze VAX jest nieco wygodniejsze dla programisty, ponieważ musi operować mniejszą liczbą mnemonik. Podejście to jest jednak mniej zwarte niż to stosowane w IBM EAS/390, ponieważ lokalizacja (rejestr czy pamięć) każdego argumentu musi być określona w rozkazie. Wrócimy do tego rozróżnienia przy okazji dyskusji na temat formatów rozkazów w rozdziale 14.
Rozpatrując zagadnienie w kategoriach działań procesora, operacje transferu danych są być może najprostszym rodzajem operacji. Jeśli zarówno źródłem, jak i miejscem docelowym są rejestry, to procesor po prostu powoduje przeniesienie danych z jednego rejestru do drugiego. Jest to operacja wewnętrzna procesora.
Tabela 13.5. Przykłady operacji transferu danychw IBM EAS/390
Mnemonik operacji
Nazwa
Liczba przenoszonych bitów
Opis
L
Load
32
przeniesienie z pamięci do rejestru
LH
Load Halfword
16
przeniesienie z pamięci do rejestru
LR
Load
32
przeniesienie z rejestru do rejestru
LER
Load (short)
32
przeniesienie z rejestru zmiennoprzecinkowego do rejestru zmiennoprzecinkowego
LE
Load (short)
32
przeniesienie z pamięci do rejestru zmiennoprzecinkowego
LDR
Load (long)
64
przeniesienie z rejestru zmiennoprzecinkowego do rejestru zmiennoprzecinkowego
LD
Load (long)
64
przeniesienie z pamięci do rejestru zmiennoprzecinkowego
ST
Store
32
przeniesienie z rejestru do pamięci
STH
Store Halfword
16
przeniesienie z rejestru do pamięci
STC
Store Character
8
przeniesienie z rejestru do pamięci
STE
Store (short)
32
przeniesienie z rejestru zmiennoprzecinkowego do pamięci
STD
Store (long)
64
przeniesienie z rejestru zmiennoprzecinkowego do pamięci
Jeśli jednak jeden lub oba argumenty znajdują się w pamięci, to procesor musi wykonać niektóre lub wszystkie z następujących działań:
1. Obliczenie adresu pamięci na podstawie trybu adresowania (omówionego w rozdziale 14).
2. Jeśli adres odnosi się do pamięci wirtualnej, to przetłumaczenie adresu z wirtualnego na rzeczywisty.
3. Określenie, czy adresowana jednostka znajduje się w pamięci podręcznej.
4. Jeśli nie, wydanie polecenia do modułu pamięci.
Operacje arytmetyczne
Większość komputerów realizuje podstawowe operacje arytmetyczne dodawania, odejmowania, mnożenia i dzielenia. Zawsze są one przewidziane dla liczb całkowitych (stałopozycyjnych) ze znakiem. Często są również przewidziane dla liczb zmiennoprzecinkowych i upakowanych liczb dziesiętnych.
Inne możliwe operacje obejmują różnego rodzaju rozkazy jednoargumentowe. Są to na przykład:
- Wartość bezwzględna: Obliczenie wartości bezwzględnej argumentu.
- Negacja: Zanegowanie argumentu.
- Zwiększenie: Zwiększenie argumentu o 1.
- Zmniejszenie: Zmniejszenie argumentu o 1.
Wykonywanie rozkazu arytmetycznego może obejmować transfer danych w celu umieszczenia ich na wejściu ALU lub wyprowadzenia ich z wyjścia ALU. Na rysunku 3.5 pokazano ruch danych objęty zarówno operacjami przesyłania danych, jak i operacjami arytmetycznymi. Ponadto, co jest oczywiste, żądana operacja jest wykonywana przez część procesora stanowiącą ALU.
Operacje logiczne
Większość komputerów umożliwia również wykonywanie różnorodnych operacji manipulowania pojedynczymi bitami słowa lub innymi jednostkami adresowalnymi, co jest często określane jako "manipulowanie bitami". Opierają się one na operacjach Boole'a (patrz rozdział 12).
Niektóre z podstawowych operacji logicznych, które można wykonać na danych boolowskich lub binarnych, zostały przedstawione w tabeli 13.6. Operacja NOT powoduje odwrócenie bitu. AND, OR oraz wykluczające OR (XOR) to najczęściej używane funkcje Boole'a z dwoma argumentami. EQUAL to przydatny test binarny.
Tabela 13.6. Podstawowe operacje Boole'a
P
Q
NOT P
P AND Q
P OR Q
P XOR Q
P = Q
0
0
1
0
0
0
1
0
1
1
0
1
1
0
1
0
0
0
1
1
0
1
1
0
1
1
0
1
Powyższe operacje Boole'a mogą być stosowane bit po bicie do n-bitowych logicznych jednostek danych. Zatem, jeśli dwa rejestry zawierają dane:
(R1) = 10100101
(R2) = 00001111
to
(R1) AND (R2) = 00000101
gdzie notacja (X) oznacza zawartość lokacji X. Zatem operacja AND może być używana jako maska, która wybiera pewne bity w słowie i zeruje pozostałe. Jako inny przykład rozważmy sytuację, gdy dwa rejestry zawierają dane:
(R1) = 10100101
(R2) = 11111111
wtedy
(R1) XOR (R2) = 01011010.
Gdy jedno ze słów składa się z samych jedynek, operacja XOR odwraca wszystkie bity w drugim słowie (uzupełnienie jedynki).
Poza bitowymi operacjami logicznymi większość maszyn umożliwia wykonywanie wielu funkcji przesuwania i rotacji (na przykład tabela 13.3c). Najbardziej podstawowe operacje zostały przedstawione na rysunku 13.6. Operacja logicznego przesunięcia polega na przesuwaniu bitów słowa w lewo lub w prawo. Na jednym z końców słowa przesuwany bit jest tracony. Na drugim końcu umieszczany jest bit 0. Przesunięcia logiczne są przydatne przede wszystkim do izolowania pól wewnątrz słowa. Zera wprowadzane do słowa zastępują niepożądaną informację, która jest wyprowadzana.
Rysunek 13.6. Operacje przesunięcia i rotacji
Załóżmy na przykład, że chcemy transmitować znaki do urządzenia we-wy metodą znak po znaku. Jeśli każde słowo pamięci ma długość 16 bitów i zawiera dwa znaki, to trzeba je odpakować przed wysłaniem. W celu wysłania dwóch znaków zawartych w jednym słowie konieczne jest:
1. Załadowanie słowa do rejestru.
2. Przesunięcie w prawo osiem razy. Powoduje to przesunięcie pozostałego znaku do prawej połowy rejestru.
3. Wykonanie operacji we-wy. Odczytanie 8 najmłodszych bitów z magistrali danych przez moduł we-wy.
W wyniku wykonania tych czynności zostanie wysłany lewy znak. W celu wysłania prawego znaku należy:
1. Załadować słowo ponownie do rejestru.
2. Wykonać operację AND z 0000000011111111. Maskuje to znaki po lewej stronie.
3. Wykonać operację we-wy.
W przypadku operacji przesunięcia arytmetycznego dane są traktowane jako liczby całkowite ze znakami, a bit znaku nie jest przesuwany. W przypadku przesunięcia arytmetycznego w prawo bit znaku jest zwykle replikowany w najbliższej pozycji bitowej na prawo. Operacje te mogą przyspieszać wykonywanie niektórych operacji arytmetycznych. W przypadku liczb w notacji dopełnienia do dwóch przesunięcie arytmetyczne w prawo odpowiada dzieleniu przez 2, z obcięciem dla liczb nieparzystych.
Zarówno arytmetyczne przesunięcie w lewo, jak i logiczne przesunięcie w lewo odpowiadają mnożeniu przez 2, pod warunkiem, że nie występuje przepełnienie. Jeśli wystąpi przepełnienie, operacje arytmetycznego i logicznego przesunięcia w lewo dają różne wyniki, jednak arytmetyczne przesunięcie w lewo zachowuje znak liczby. Ze względu na możliwość wystąpienia przepełnienia wiele procesorów nie zawiera tego rozkazu, w tym PowerPC i Itanium. Pozostałe, takie jak IBM EAS/390, obsługują ten rozkaz. Co ciekawe, architektura x86 zawiera arytmetyczne przesunięcie w lewo, jednak zdefiniowane jako identyczne z logicznym przesunięciem w lewo.
Operacje obracania lub cyklicznego przesuwania zachowują wszystkie bity, na których wykonywana jest operacja. Jednym z zastosowań rotacji jest wprowadzanie każdego bitu kolejno do skrajnego lewego bitu, gdzie można go zidentyfikować, testując znak danych (traktowany jako liczba).
Podobnie jak w przypadku operacji arytmetycznych, operacje logiczne angażują jednostkę ALU oraz mogą obejmować operacje transferu danych. W tabeli 13.7 podano przykłady wszystkich operacji przesunięcia i rotacji omówionych w tym podrozdziale.
Tabela 13.7. Przykłady operacji przesunięcia i obrotu
Wejście
Operacja
Wynik
10100110
Logiczne przesunięcie w prawo (3 bity)
00010100
10100110
Logiczne przesunięcie w lewo (3 bity)
00110000
10100110
Arytmetyczne przesunięcie w prawo (3 bity)
11110100
10100110
Arytmetyczne przesunięcie w lewo (3 bity)
10110000
10100110
Rotacja w prawo (3 bity)
11010100
10100110
Rotacja w lewo (3 bity)
00110101
Konwersja
Rozkazy konwersji są rozkazami, które zmieniają format lub operują na formatach danych. Przykładem jest konwersja liczb z dziesiętnych na binarne. Przykładem bardziej złożonego rozkazu zmiany formatu jest rozkaz tłumaczenia (TR, ang. translate) stosowany w EAS/390. Może on być użyty do konwersji jednego kodu 8-bitowego na inny i operuje na trzech argumentach:
TR R1 (L), R2
Argument R2 zawiera adres początkowy tablicy kodów 8-bitowych. Tłumaczone są bajty L począwszy od adresu określonego w R1, przy czym każdy bajt jest zastępowany zawartością wpisu w tablicy indeksowanego przez ten bajt. Na przykład, w celu wykonania tłumaczenia z EBCDIC na IRA, najpierw należy utworzyć 256-bajtową tablicę w lokacjach pamięci oznaczonych, powiedzmy 1000-10FF w notacji szesnastkowej. Tablica zawiera znaki w kodzie IRA w sekwencji reprezentacji binarnej kodu EBCDIC. Oznacza to, że kod IRA jest umieszczany w tablicy w położeniu względnym równym binarnej wartości kodu EBCDIC tego samego znaku. Wobec tego lokacje od 10F0 do 10F9 będą zawierały wartości od 30 do 39, ponieważ F0 jest kodem EBCDIC cyfry 0, 30 jest kodem IRA cyfry 0 i tak dalej, aż do cyfry 9. Załóżmy teraz, że mamy kod EBCDIC dla cyfr 1984 począwszy od lokacji 2100 i chcemy go przekonwertować na kod IRA. Załóżmy, że:
- Lokacje od 2100 do 2103 zawierają F1 F9 F8 F4.
- R1 zawiera 2100.
- R2 zawiera 1000.
Jeśli zatem wykonamy operację
TR R1 (4), R2
to lokacje od 2100 do 2103 będą zawierały 31 39 38 34.
We-wy
Rozkazy dotyczące we-wy dość szczegółowo omówiliśmy w rozdziale 8 (tom I). Jak widzieliśmy, istnieje wiele rozwiązań obejmujących izolowane programowane we-wy, programowane we-wy odwzorowane w pamięci, DMA oraz procesory we-wy. W wielu implementacjach przewidziano tylko niewielką liczbę rozkazów we-wy, przy czym poszczególne operacje są określane za pomocą parametrów, kodów lub słów rozkazu.
Sterowanie systemowe
Rozkazy sterowania systemowego to te, które mogą być wykonywane tylko wtedy, gdy procesor znajduje się w określonym stanie uprzywilejowanym lub wykonuje program w specjalnym uprzywilejowanym obszarze pamięci. Zazwyczaj rozkazy te są zarezerwowane do użytku systemu operacyjnego.
Oto kilka przykładów operacji sterowania systemowego. Rozkaz sterowania systemowego może odczytywać lub zmieniać rejestr sterowania (rejestry sterowania omówimy w rozdziale 16). Innym przykładem jest rozkaz odczytu lub modyfikacji klucza ochrony pamięci, jak ten stosowany w systemie pamięci EAS/390. Jeszcze innym przykładem jest dostęp do bloków sterowania procesem w systemie wieloprogramowym.
Przeniesienie sterowania
W przypadku wszystkich omówionych do tej pory typów operacji kolejnym rozkazem do wykonania jest ten, który następuje w pamięci bezpośrednio po bieżącym rozkazie. Jednak znaczna część poleceń dowolnego programu ma za zadanie zmienić sekwencję wykonywania rozkazów. W wyniku realizacji tych rozkazów procesor aktualizuje wartość licznika programu tak, by określała ona adres pewnego rozkazu umieszczonego w pamięci.
Istnieje wiele powodów, dla których wymagane są operacje przekazania sterowania. Do najważniejszych należą:
1. Podczas praktycznego używania komputerów podstawowe znaczenie ma możliwość wykonywania każdego rozkazu więcej niż raz, a być może nawet wiele tysięcy razy. Istnieją zastosowania wymagające użycia tysięcy lub nawet milionów rozkazów. Byłoby to absolutnie niepraktyczne, by odrębnie wypisywać każdy rozkaz. Jeśli ma być przetwarzana tablica lub lista jednostek danych, to wymagana jest pętla programowa. W takim przypadku jedna sekwencja rozkazów jest wykonywana cyklicznie, aż do momentu przetworzenia wszystkich danych.
2. Praktycznie wszystkie programy obejmują podejmowanie decyzji. Chcemy, żeby komputer wykonywał określone działania w pewnych warunkach, inne zaś działania w innych warunkach. Załóżmy na przykład, że sekwencja rozkazów powoduje obliczanie pierwiastka kwadratowego pewnej liczby. Na początku sekwencji badany jest znak liczby i jeśli jest ona ujemna, to obliczenia nie są przeprowadzane i sygnalizowane jest wystąpienie błędu.
3. Poprawne skomponowanie dużego lub nawet średniego programu komputerowego jest niezwykle trudnym zadaniem. Jest korzystne, jeśli istnieją mechanizmy dzielenia zadania na mniejsze części, nad którymi można pracować pojedynczo.
Przedyskutujemy teraz operacje przekazywania sterowania najczęściej spotykane w listach rozkazów: rozgałęzianie, pominięcie oraz wywołanie podprogramu.
INSTRUKCJE ROZGAŁĘZIENIA. Instrukcja rozgałęzienia, zwana także instrukcją skoku, zawiera jako jeden ze swoich argumentów adres kolejnego rozkazu do wykonania. Najczęściej jest to rozkaz warunkowego rozgałęzienia. Oznacza to, że rozgałęzienie (aktualizacja licznika programu do adresu określonego przez argument) jest tworzone tylko wtedy, gdy spełniony jest określony warunek. W przeciwnym razie wykonywany jest kolejny rozkaz z sekwencji, który jak zwykle zwiększa licznik programu. Rozgałęzienie, które jest zawsze wykonywane, jest określane jako rozgałęzienie bezwarunkowe.
Istnieją dwa popularne sposoby generowania warunku, które mają zostać przetestowane w rozkazach rozgałęzienia warunkowego. Po pierwsze, większość maszyn udostępnia jedno- lub wielobitowy kod warunku, który jest ustawiany jako wynik niektórych operacji. Ten kod można traktować jako krótki rejestr widoczny dla użytkownika. Na przykład podczas wykonania operacji arytmetycznej (ADD, SUBTRACT itd.) może zostać ustawiony 2-bitowy kod warunku, który może przyjąć jedną z następujących czterech wartości: 0, dodatni, negatywny, przepełnienie. Na takim komputerze mogą istnieć cztery różne rozkazy rozgałęzienia warunkowego:
BRP X Rozgałęzienie do lokacji X, jeśli wynik jest dodatni.
BRN X Rozgałęzienie do lokacji X, jeśli wynik jest ujemny.
BRZ X Rozgałęzienie do lokacji X, jeśli wynik jest zerem.
BRO X Rozgałęzienie do lokacji X, jeśli występuje przepełnienie.
We wszystkich tych przypadkach wynik, o którym mowa, jest wynikiem ostatnio wykonanej operacji, w ramach której został też ustalony kod warunku.
Innym sposobem, który można stosować łącznie z trójadresowym formatem rozkazu, jest wykonanie porównania i określenie rozgałęzienia w ramach jednego rozkazu. Na przykład:
BRE R1, R2, X rozgałęzienie do X, jeśli zawartość R1 = zawartość R2.
Na rysunku 13.7 pokazano przykłady takich operacji. Zauważmy, że rozgałęzienie może następować do przodu (do rozkazu o wyższym adresie) lub wstecz (do niższego adresu). Przykład pokazuje, jak można użyć rozgałęzień bezwarunkowych i warunkowych w celu tworzenia cyklicznej pętli rozkazów. Rozkazy w lokacjach od 202 do 210 będą wykonywane powtarzalnie aż do momentu, kiedy wynikiem odejmowania Y od X będzie 0.
Rysunek 13.7. Rozkazy rozgałęzienia
ROZKAZ POMINIĘCIA. Inną często spotykaną formą rozkazu przekazania sterowania jest rozkaz pominięcia. Ten typ rozkazu zawiera adres domyślny. Zazwyczaj rozkaz pominięcia oznacza pominięcie jednego rozkazu, a zatem adres domyślny jest równy adresowi kolejnego rozkazu plus długość jednego rozkazu. Ponieważ rozkaz pominięcia nie wymaga określania adresu miejsca docelowego, może on pomieścić dodatkowe operacje. Typowym przykładem jest rozkaz zwiększ-i-pomiń-jeśli-zero (ISZ, ang. increment-and-skip-if-zero). Rozważmy następujący fragment programu:
301
...
309 ISZ R1
310 BR 301
311
W tym programie użyto dwóch rozkazów przekazania sterowania w celu zrealizowania pętli iteracyjnej. Wartość R1 jest ustalana za pomocą zanegowanej liczby iteracji, które mają być przeprowadzone. Na jednym końcu pętli następuje zwiększenie wartości R1. Jeśli R1 nie jest równe zeru, to program rozgałęzia się do tyłu, na początek pętli. W przeciwnym razie rozgałęzienie jest pomijane, a program jest kontynuowany i wykonywany jest rozkaz następujący po zakończeniu pętli.
ROZKAZY WYWOŁANIA PODPROGRAMU. Być może najważniejszą innowacją w rozwoju języków programowania jest podprogram (ang. procedure), który jest samodzielnym programem komputerowym, wbudowywanym do większego programu. Podprogram może być wywołany w dowolnym punkcie programu. Oznacza to, że w tym punkcie rozkazuje się komputerowi zrealizowanie całego podprogramu, a następnie powrót do punktu, w którym nastąpiło wywołanie.
Dwoma zasadniczymi powodami stosowania podprogramów są ekonomia i modułowość. Dzięki podprogramowi ten sam fragment kodu może zostać użyty wielokrotnie. Jest to istotne dla kosztów programowania oraz dla efektywniejszego wykorzystania przestrzeni pamięci w systemie (program musi być gdzieś przechowywany). Podprogramy umożliwiają również podział dużych zadań programistycznych na mniejsze części. Tak rozumiana modułowość znacznie ułatwia programowanie.
Mechanizm podprogramu standardowego obejmuje dwa podstawowe rozkazy: rozkaz wywołania, który powoduje skok z aktualnej lokacji do podprogramu, oraz rozkaz powrotu powodujący powrót z podprogramu do miejsca, w którym nastąpiło wywołanie. Oba te rozkazy są rozkazami rozgałęzień.
Na rysunku 13.8a przedstawiono wykorzystanie podprogramów do tworzenia programu. W analizowanym przykładzie główny program rozpoczyna się w lokacji 4000. Program główny zawiera wywołanie podprogramu SUB1, rozpoczynającego się od lokacji 4500. Gdy następuje rozkaz wywołania, procesor zawiesza realizację programu głównego i rozpoczyna wykonywanie SUB1, pobierając kolejny rozkaz z lokacji 4500. Wewnątrz SUB1 istnieją dwa wywołania do podprogramu SUB2 w lokacji 4800. W każdym przypadku wykonywanie SUB1 jest zawieszane i wykonywany jest podprogram SUB2. Rozkaz powrotu RETURN powoduje powrót procesora do programu wywołującego i kontynuowanie jego wykonywania począwszy od rozkazu następującego po rozkazie wywołania (CALL). Zachowanie to zostało zilustrowane na rysunku 13.8b.
Warto wspomnieć o trzech sprawach:
1. Podprogram można wywołać z więcej niż jednej lokacji.
2. W podprogramie również mogą pojawiać się wywołania podprogramów. Pozwala to na ich zagnieżdżanie na dowolną głębokość.
3. Każdemu wywołaniu podprogramu odpowiada powrót zawarty w wywoływanym programie.
Ponieważ pożądana jest możliwość wywoływania podprogramu z wielu punktów, więc procesor musi w pewien sposób zapisać adres zwrotny, by powrót mógł nastąpić prawidłowo. Istnieją trzy wspólne miejsca przechowywania adresu zwrotnego:
- rejestr,
- początek podprogramu,
- wierzchołek stosu.
Rysunek 13.8. Podprogramy zagnieżdżone
Rozważmy rozkaz w języku maszynowym CALL X, który oznacza wywołaj podprogram w lokacji X. Jeśli wykorzystywany jest rejestr, to CALL X powoduje następujące działania:
RN ? PC + ?
PC ? X
gdzie RN jest rejestrem, który jest zawsze używany do tego celu, PC jest licznikiem programu, a ? - długością rozkazu. Wywołany podprogram może teraz zachować zawartość RN w celu jej użycia przy późniejszym powrocie.
Drugą możliwością jest przechowywanie adresu powrotu na początku podprogramu. W tym przypadku rozkaz CALL X powoduje:
X ? PC + ?
PC ? X + 1
Jest to całkiem poręczne rozwiązanie. Adres powrotu został bezpiecznie zachowany.
Oba powyższe rozwiązania działają i były już stosowane. Jedynym ich ograniczeniem jest to, że komplikują stosowanie podprogramów wielowejściowych (ang. reentrant). Podprogram wielowejściowy to taki, w którym możliwe jest jednoczesne otwarcie kilku wywołań. Przykładem użycia takiej właściwości jest procedura rekurencyjna, czyli taka, która wywołuje samą siebie (patrz dodatek F). Jeśli parametry takiego podprogramu są przekazywane przez rejestry lub pamięć, to musi istnieć pewien kod odpowiedzialny za zapisanie tych parametrów tak, by rejestry lub przestrzeń pamięci były dostępne dla innych wywołań podprogramów.
Bardziej ogólnym i efektywniejszym rozwiązaniem jest użycie stosu (patrz dodatek E). Kiedy procesor wykonuje wywołanie, umieszcza adres powrotu na stosie. Kiedy wykonuje powrót, używa adresu ze stosu. Wykorzystanie stosu przedstawiono na rysunku 13.9.
Rysunek 13.9. Użycie stosu do implementacji zagnieżdżonych podprogramów z rysunku 13.8
Poza zachowaniem adresu powrotu często wraz z wywołaniem podprogramu konieczne jest również przekazywanie parametrów. Mogą one być przekazywane w rejestrach. Inną możliwością jest przechowywanie parametrów w pamięci bezpośrednio po rozkazie wywołania. W takim przypadku powrót musi następować do lokacji poza parametrami. Oba te rozwiązania mają jednak wady. Jeśli używane są rejestry, to programy wywołany i wywołujący muszą zostać napisane tak, by rejestry były właściwie używane. Przechowywanie parametrów w pamięci utrudnia wymianę zmiennej liczby parametrów. Poza tym oba rozwiązania uniemożliwiają użycie podprogramów wielowejściowych.
Bardziej elastycznym podejściem do przekazywania parametrów jest wykorzystanie stosu. Gdy procesor realizuje wywołanie, przekazuje na stos nie tylko adres powrotu, ale także parametry, które mają zostać przekazane do podprogramu. Wywołany podprogram może uzyskać dostęp do parametrów umieszczonych na stosie. W czasie powrotu parametry powrotne również mogą zostać umieszczone na stosie. Cały zestaw parametrów, łącznie z adresem powrotu, który jest przechowywany w celu wywołania procedury, jest określany jako ramka stosu (ang. stack frame).
Przykład takiego rozwiązania został pokazany na rysunku 13.10. Odnosi się on do podprogramu P, w którym zdeklarowano dwie zmienne lokalne x1 i x2, oraz do podprogramu Q, który może być wywołany przez P i w którym zdeklarowano zmienne lokalne y1 i y2. Na rysunku punktem powrotu każdej procedury jest pierwsza jednostka przechowywana w odpowiedniej ramce stosu. Następną przechowywaną jednostką jest wskaźnik początku poprzedniej ramki. Jest to konieczne, jeśli liczba parametrów na stosie lub ich długość są zmienne.
Rysunek 13.10. Wzrost ramek stosu przy użyciu przykładowych podprogramów P oraz Q
13.5. RODZAJE OPERACJI INTEL X86 I ARM
Rodzaje operacji w architekturze x86
Architektura x86 zapewnia złożony zestaw rodzajów operacji, w tym wiele wyspecjalizowanych rozkazów. Intencją było dostarczenie narzędzi autorom programów kompilujących w celu optymalnego tłumaczenia programów w języku wysokiego poziomu na język maszynowy. Większość z nich to konwencjonalne rozkazy znajdujące się na większości list rozkazów maszynowych, jednak kilka rodzajów rozkazów zostało dostosowanych do architektury x86 i są one szczególnie interesujące. W dodatku A do [CART06] wymieniono listę rozkazów x86 wraz z argumentami dla każdego z nich oraz wpływem rozkazów na kody warunków. Dodatek B podręcznika języka asemblera NASM [NASM17] zawiera bardziej szczegółowy opis każdego rozkazu x86. Oba dokumenty są dostępne na stronie williamstallings.com/ComputerOrganization/COA11e.
ROZKAZY WYWOŁANIA/POWROTU. x86 udostępnia cztery rozkazy do obsługi procedury wywołania/powrotu: CALL, ENTER, LEAVE, RETURN. Pouczające jest również zapoznanie się z działaniem tych rozkazów. Przypomnij sobie z rysunku 13.10, że powszechnym sposobem implementacji procedury wywołania/powrotu jest użycie ramek stosu. Gdy wywoływana jest nowa procedura, muszą być wykonane następujące działania podczas przechodzenia do nowej procedury:
- Umieszczenie punktu powrotu na stosie.
- Umieszczenie bieżącego wskaźnika ramki na stosie.
- Skopiowanie wskaźnika stosu jako nowej wartości wskaźnika ramki.
- Skorygowanie wskaźnika stosu w celu alokacji ramki.
Rozkaz CALL powoduje umieszczenie bieżącej wartości wskaźnika rozkazu na stosie oraz skok do początkowego punktu procedury przez umieszczenie adresu tego punktu we wskaźniku rozkazów. W maszynach 8088 i 8086 typowa procedura rozpoczynała się od sekwencji
PUSH EBP
MOV EBP, ESP
SUB ESP, space_for_locals
gdzie EBP jest wskaźnikiem ramki, a ESP - wskaźnikiem stosu. W procesorze 80286 i w późniejszych procesorach rozkaz ENTER powodował wykonanie wszystkich powyższych operacji za pomocą jednego rozkazu.
Rozkaz ENTER został dodany do listy rozkazów w celu zapewnienia bezpośredniego wsparcia kompilatora. Rozkaz ten umożliwia realizację tzw. procedur zagnieżdżonych w takich językach, jak Pascal, COBOL i Ada (niewystępujących w C i w FORTRAN-ie). Okazało się, że istnieją lepsze sposoby postępowania z procedurami zagnieżdżonymi dla tych właśnie języków. Co więcej, chociaż rozkaz ENTER oszczędza kilka bajtów pamięci w porównaniu z sekwencją PUSH, MOV, SUB (4 bajty w porównaniu z 6 bajtami), to w rzeczywistości czas wykonania jest dłuższy (10 cykli zegara w porównaniu z 6 cyklami zegara). Zatem, chociaż projektantom listy rozkazów mogło się wydawać to dobrym pomysłem, to komplikuje to implementację procesora, zapewniając jedynie niewielkie lub żadne korzyści. Zobaczymy, że w przeciwieństwie do tego rozwiązanie RISC przy projektowaniu procesora zapobiega tak złożonym rozkazom, jak ENTER, umożliwiając jednocześnie efektywniejszą implementację za pomocą sekwencji prostszych rozkazów.
ZARZĄDZANIE PAMIĘCIĄ. Kolejny zestaw wyspecjalizowanych rozkazów dotyczy segmentacji pamięci. Są to rozkazy uprzywilejowane, które można wykonać tylko z poziomu systemu operacyjnego. Umożliwiają ładowanie i odczytywanie lokalnych i globalnych tablic segmentów (zwanych tablicami deskryptorów) oraz sprawdzanie i zmienianie poziomu uprawnień segmentu.
Rozkazy specjalne dotyczące wewnętrznej pamięci podręcznej zostały omówione w rozdziale 5 (tom I).
ZNACZNIKI STANU I KODY WARUNKOWE. Znaczniki stanu to bity w specjalnych rejestrach, które mogą być ustawiane przez określone operacje i używane w rozkazach rozgałęzień warunkowych. Termin kod warunkowy odnosi się do ustawień jednej lub większej liczby znaczników stanu. W x86 i wielu innych architekturach znaczniki stanu są ustawiane przez operacje arytmetyczne i porównania. Operacja porównania w większości języków polega na odjęciu dwóch argumentów, podobnie jak w operacji odejmowania. Różnica polega na tym, że operacja porównania ustawia jedynie znaczniki stanu, podczas gdy operacja odejmowania przechowuje również wynik odejmowania w argumencie docelowym. Niektóre architektury ustawiają również znaczniki stanu dla rozkazów dotyczących transferu danych.
Listę znaczników stanu, która jest używana w architekturze x86, przedstawiono w tabeli 13.8. Każdy znacznik lub ich kombinację można przetestować względem skoku warunkowego. Kody warunkowe (kombinacje wartości znaczników stanu), dla których zdefiniowano kody operacji skoku warunkowego, przedstawiono w tabeli 13.9.
Na podstawie tej listy można poczynić kilka interesujących spostrzeżeń. Po pierwsze, może być potrzebne sprawdzenie dwóch argumentów w celu stwierdzenia, czy jedna liczba jest większa od drugiej. Zależy to jednak od tego, czy liczby te mają znaki. Na przykład 8-bitowa liczba 11111111 jest większa od 00000000, jeśli obie liczby są interpretowane jako bezznakowe liczby całkowite (255 > 0), jest jednak mniejsza, jeśli są traktowane jako 8-bitowe liczby w notacji uzupełnienia do dwóch (-1 < 0). Z tego powodu w wielu językach asemblera wprowadzono dwa zestawy wyrażeń w celu odróżnienia tych przypadków. Jeśli dwie liczby są porównywane jako liczby całkowite ze znakiem, to używane są wyrażenia większa niż lub mniejsza niż. Jeśli natomiast są porównywane jako liczby całkowite bezznakowe, to używane są wyrażenia poniżej i ponad.
Drugie spostrzeżenie wiąże się ze złożonością porównywania liczb całkowitych ze znakami. Wynik ze znakiem jest większy lub równy zeru, jeżeli (1) bit znaku jest zerem i nie wystąpiło przepełnienie (S = 0 AND O = 0), lub (2) bit znaku jest jedynką i wystąpiło przepełnienie. Analizując rysunek 11.4 (tom I), można przekonać się, że warunki określone dla różnych operacji ze znakami są właściwe.
Tabela 13.8. Znaczniki stanu x86
Bit stanu
Nazwa
Opis
C
Carry
Wskazuje przeniesienie lub przeniesienie zanegowane do lewej pozycji bitowej w wyniku operacji arytmetycznej. Modyfikowany również przez niektóre operacje przesunięcia lub obrotu.
P
Parity
Parzystość wyniku operacji arytmetycznej lub logicznej, 1 wskazuje parzystość, 0 - nieparzystość.
A
Auxiliary Carry
Reprezentuje przeniesienie lub przeniesienie zanegowane między półbajtami w wyniku 8-bitowej operacji arytmetycznej lub logicznej. Używany w arytmetyce dziesiętnej kodowanej binarnie.
Z
Zero
Wskazuje, że wynik operacji arytmetycznej lub logicznej wynosi 0.
S
Sign
Wskazuje znak operacji arytmetycznej lub logicznej.
O
Overflow
Wskazuje przepełnienie arytmetyczne po dodawaniu lub odejmowaniu w reprezentacji uzupełnienia do dwóch.
Tabela 13.9. Kody warunkowe dla rozkazów skoku warunkowego i rozkazów SETcc w architekturze x86
Symbol
Sprawdzany warunek
Komentarz
A, NBE
C = 0 AND Z = 0
ponad, nie poniżej lub równe (większa niż, bez znaku)
AE, NB, NC
C = 0
ponad lub równe, nie poniżej (większa niż lub równa,
bez znaku), bez przeniesienia
B, NAE, C
C = 1
poniżej, nie powyżej lub równe (mniejsza niż, bez
znaku), ustawione przeniesienie
BE, NA
C = 1 OR Z = 1
poniżej lub równe, nie powyżej (mniejsza lub równa,
bez znaku)
E, Z
Z = 1
równe, zero (ze znakiem lub bez znaku)
G, NLE
[(S = 1 AND O = 1) OR (S = 0
AND O = 0)]AND[Z = 0]
większa niż, nie mniejsza lub równa (ze znakiem)
GE, NL
(S = 1 AND O = 1) OR (S = 0
AND O = 0)
większa niż lub równa, nie mniejsza niż (ze znakiem)
L, NGE
(S = 1 AND O = 0) OR (S = 0
AND O = 0)
mniejsza niż, nie większa lub równa (ze znakiem)
LE, NG
(S = 1 AND O = 0) OR (S = 0
AND O = 1) OR (Z = 1)
mniejsza niż lub równa, nie większa (ze znakiem)
NE, NZ
Z = 0
nie równa, nie zero (ze znakiem lub bez znaku)
NO
O = 0
brak przepełnienia
NS
S = 0
nie znak (nie ujemna)
NP, PO
P = 0
brak parzystości, nieparzystość
O
O = 1
przepełnienie
P
P = 1
parzystość
S
S = 1
znak (ujemna)
ROZKAZY SIMD x86. W roku 1996 Intel wprowadził technologię MMX do swojej linii produktów Pentium. MMX to lista rozkazów ściśle zoptymalizowanych do zadań multimedialnych. Pojawiło się 57 nowych rozkazów, które traktują dane analogicznie do SIMD (single-instruction, multiple-data), co umożliwia wykonanie tego samego rozkazu, jak dodawanie lub mnożenie, jednocześnie na wielu elementach danych. Wykonanie każdego rozkazu zajmuje zwykle jeden cykl zegara. W przypadku prawidłowego wykorzystania przyspieszenie wynikające z tych szybkich operacji równoległych może być od dwóch do ośmiu razy większe w porównaniu ze zbliżonymi algorytmami, które nie używają rozkazów MMX [ATKI96]. Wraz z wprowadzeniem 64-bitowej architektury x86 firma Intel dokonała dalszego rozszerzenia o argumenty w postaci podwójnych słów poczwórnych (128 bitów) oraz o operacje zmiennoprzecinkowe. W tym podrozdziale przeanalizujemy właściwości MMX.
Głównym celem MMX jest programowanie zorientowane na multimedia. Dane wideo i audio zazwyczaj składają się z dużych macierzy małych danych, o rozmiarze 8 lub 16 bitów, podczas gdy konwencjonalne rozkazy są przystosowane do działania na danych 32- lub 64-bitowych. Można podać kilka przykładów. W przetwarzaniu grafiki i wideo pojedyncza scena składa się z macierzy pikseli[2], w której na każdy piksel lub na każdy składnik koloru piksela (czerwony, zielony, niebieski) przypada 8 bitów. Typowe próbki audio są kwantowane przy użyciu 16 bitów. W przypadku niektórych algorytmów graficznych 3D podstawowe typy danych mają rozmiar 32 bitów. W celu umożliwienia równoległego działania na danych o takim rozmiarze, w technologii MMX zdefiniowano trzy nowe rodzaje danych. Każdy z nich ma długość 64 bitów i składa się z wielu mniejszych pól danych, z których każde przechowuje liczbę całkowitą stałoprzecinkową. Są to następujące rodzaje danych:
- Upakowany bajt: Osiem bajtów upakowanych w 64-bity.
- Słowo upakowane: Cztery 16-bitowe słowa upakowane w 64 bity.
- Upakowane słowo podwójne: Dwa 32-bitowe słowa podwójne upakowane w 64 bity.
W tabeli 13.10 przedstawiono listę rozkazów MMX. Większość z nich obejmuje równoległe operacje wykonywane na bajtach, słowach lub słowach podwójnych. Na przykład rozkaz PSLLW wykonuje przesunięcie logiczne w lewo oddzielnie na każdym z czterech słów w upakowanym słowie argumentu. Z kolei rozkaz PADDB przyjmuje za dane wejściowe argumenty w postaci upakowanych bajtów i wykonuje równoległe dodawanie na każdej pozycji bajta niezależnie w celu otrzymania na wyjściu wyniku w postaci upakowanych bajtów.
Niezwykłą cechą nowej listy rozkazów jest wprowadzenie arytmetyki z nasyceniem (ang. saturation arithmetic) dla argumentów bajtowych i 16-bitowych. W przypadku zwykłej bezznakowej arytmetyki, w przypadku wystąpienia przepełnienia (tzn. przeniesienia najbardziej znaczącego bitu), dodatkowy bit jest obcinany. Nazywa się to zawijaniem, ponieważ efektem obcięcia może być na przykład uzyskanie wyniku dodawania, który jest mniejszy niż dwa argumenty wejściowe. Rozważmy dodawanie dwóch słów w systemie szesnastkowym: F000h i 3000h. Suma byłaby wyrażona jako
F000h = 1111 0000 0000 0000
+ 3000h = 0011 0000 0000 0000
10010 0000 0000 0000 = 2000h
Jeżeli te dwie liczby reprezentowałyby intensywność obrazu, to efektem dodawania byłoby to, że kombinacja dwóch ciemnych odcieni dałaby w rezultacie odcień jaśniejszy. Zazwyczaj nie jest to zamierzone. W przypadku arytmetyki z nasyceniem, jeśli dodawanie skutkuje przepełnieniem lub odejmowaniem z niedomiarem, to wynik jest ustawiany jako największa lub najmniejsza możliwa do reprezentowania wartość. Stosując arytmetykę z nasyceniem do poprzedniego przykładu, mamy
F000h = 1111 0000 0000 0000
+ 3000h = 0011 0000 0000 0000
10010 0000 0000 0000
1111 1111 1111 1111 = FFFFh
Tabela 13.10. Lista rozkazów MMX
Kategoria
Rozkaz
Opis
Operacja arytmetyczna
PADD [B, W, D]
Równoległe dodawanie upakowanych ośmiu bajtów, czterech 16-bitowych słów lub dwóch 32-bitowych słów podwójnych, z zawijaniem.
PADDS [B, W]
Dodawanie z nasyceniem.
PADDUS [B, W]
Dodawanie bezznakowe z nasyceniem.
PSUB [B, W, D]
Odejmowanie z zawijaniem.
PSUBS [B, W]
Odejmowanie z nasyceniem.
PSUBUS [B, W]
Odejmowanie bezznakowe z nasyceniem.
PMULHW
Równoległe mnożenie czterech 16-bitowych słów ze znakiem, z wybranymi 16 bitami wyższego rzędu 32-bitowego wyniku.
PMULLW
Równoległe mnożenie czterech 16-bitowych słów ze znakiem, z wybranymi 16 bitami niższego rzędu 32-bitowego wyniku.
PMADDWD
Mnożenie równoległe czterech 16-bitowych słów ze znakiem, zsumowanie sąsiednich par 32-bitowych wyników.
Porównanie
PCMPEQ [B, W, D]
Porównanie równoległe pod kątem równości, wynik jest maską złożoną z jedynek dla prawdy lub zer dla fałszu.
PCMPGT [B, W, D]
Porównanie równoległe "większe niż", wynik jest maską złożoną z jedynek dla prawdy lub zer dla fałszu.
Konwersja
PACKUSWB
Spakowanie słów w bajty z nasyceniem, bez znaku.
PACKSS [WB, DW]
Spakowanie słów w bajty lub słów podwójnych w słowa ze znakiem, z nasyceniem.
PUNPCKH [BW, WD, DQ]
Równoległe rozpakowywanie (scalanie z przeplotem) bajtów wyższego rzędu, słów lub słów podwójnych z rejestru MMX.
PUNPCKL [BW, WD, DQ]
Równoległe rozpakowywanie (scalanie z przeplotem) bajtów, słów lub słów podwójnych niższego rzędu z rejestru MMX.
Operacja logiczna
PAND
64-bitowa operacja logiczna AND, bit po bicie
PNDN
64-bitowa operacja logiczna AND NOT, bit po bicie
POR
64-bitowy operacja logiczna OR, bit po bicie
PXOR
64-bitowe operacja logiczna XOR, bit po bicie
Przesunięcie
PSLL [W, D, Q]
Równoległe logiczne przesunięcie w lewo upakowanych słów, słów podwójnych lub poczwórnych o wartość określoną w rejestrze MMX lub wartość natychmiastową.
PSRL [W, D, Q]
Równoległe logiczne przesunięcie w prawo upakowanych słów, słów podwójnych lub słów poczwórnych.
PSRA [W, D]
Równoległe przesunięcie w prawo upakowanych słów, słów podwójnych lub słów poczwórnych.
Transfer danych
MOV [D, Q]
Przeniesienie słowa podwójnego lub poczwórnego do/z rejestru MMX.
Zarządzanie stanem
EMMS
Pusty stan MMX (puste znaczniki bitów rejestru FP).
Uwaga: Jeśli rozkaz obsługuje wiele rodzajów danych [bajt (B), słowo (W), podwójne słowo (D), poczwórne słowo (Q)], to rodzaj danych został wskazany w nawiasach.
Aby lepiej zrozumieć wykorzystanie rozkazów MMX, przyjrzyjmy się przykładowi zaczerpniętemu z pracy [PELE97]. Popularnym zastosowaniem w obróbce wideo jest efekt zanikania: ściemniania i rozjaśniania, co jest wykorzystywane do stopniowego przejścia od jednej sceny do drugiej. Dwa obrazy łączy się, stosując średnią ważoną:
Piksel_wynikowy = Piksel_A × zanikanie + Piksel_B × (1 - zanikanie)
To obliczenie jest wykonywane dla każdego położenia piksela A i B. Jeśli seria klatek wideo jest tworzona przy stopniowej zmianie wartości zanikania od 1 do 0 (skalowanej odpowiednio dla 8-bitowej liczby całkowitej), wynikiem jest zanikanie obrazu A i przechodzenie do obrazu B.
Na rysunku 13.11 przedstawiono sekwencję kroków wymaganych dla jednego zbioru pikseli. Składowe piksela 8-bitowego są konwertowane na elementy 16-bitowe,
Rysunek 13.11. Tworzenie obrazu na reprezentacji płaszczyzn kolorów
w celu dostosowania do 16-bitowej funkcji mnożenia MMX. Jeśli przetwarzane obrazy są w rozdzielczości 640 × 480, a technika tworzenia efektu przenikania wykorzystuje wszystkie 255 możliwych wartości zanikania, to całkowita liczba rozkazów wykonanych przy użyciu MMX wynosi 535 milionów. Te same obliczenia, wykonane bez stosowania rozkazów MMX, wymagają wykonania 1,4 miliarda rozkazów [INTE98].
Rodzaje operacji w architekturze ARM
Architektura ARM zapewnia dużą kolekcję rodzajów operacji. Oto główne kategorie:
- Rozkazy ładowania i przechowywania: W architekturze ARM jedynie rozkazy dotyczące ładowania i przechowywania uzyskują dostęp do lokacji pamięci. Rozkazy arytmetyczne i logiczne są wykonywane tylko na rejestrach i wartościach natychmiastowych zakodowanych w rozkazie. Takie ograniczenie jest charakterystyczne dla projektu zgodnego z RISC i zostało omówione w rozdziale 17. Architektura ARM obsługuje dwa ogólne typy rozkazów, które ładują lub przechowują wartość pojedynczego rejestru lub pary rejestrów z lub do pamięci: (1) ładowanie lub zapisanie 32-bitowego słowa lub 8-bitowego bajta bez znaku oraz (2) ładowanie lub zapisanie 16-bitowego półsłowa bez znaku oraz ładowanie i rozszerzenie znaku 16-bitowego półsłowa lub 8-bitowego bajta.
- Rozkazy rozgałęzienia: ARM obsługuje rozkazy rozgałęzienia, które umożliwiają warunkowe rozgałęzienie do przodu lub do tyłu, do 32 MB. Wywołanie podprogramu może być wykonane przez standardowy rozkaz rozgałęzienia. Oprócz umożliwienia rozgałęzienia do przodu lub do tyłu o wartość do 32 MB, rozkaz Branch with Link (BL) zachowuje adres rozkazu po rozgałęzieniu (adres zwrotny) w LR (R14). Rozgałęzienia są określane przez 4-bitowe pole warunkowe w rozkazie.
- Rozkazy przetwarzania danych: Ta kategoria obejmuje rozkazy logiczne (AND, OR, XOR), rozkazy dodawania i odejmowania oraz rozkazy testowania i porównywania.
- Rozkazy mnożenia: Rozkazy mnożenia liczb całkowitych dotyczą operacji na argumentach w postaci słów lub półsłów i mogą dawać normalne lub długie wyniki. Na przykład istnieje rozkaz mnożenia, który pobiera dwa 32-bitowe argumenty i daje wynik 64-bitowy.
- Rozkazy równoległego dodawania i odejmowania: Oprócz normalnych rozkazów przetwarzania danych i mnożenia istnieje lista rozkazów równoległego dodawania i odejmowania, w przypadku których operacje są wykonywane na dwóch częściach argumentów równolegle. Na przykład rozkaz ADD16 dodaje górne półsłowa dwóch rejestrów, by utworzyć górne półsłowa wyniku oraz dodaje dolne półsłowa tych samych dwóch rejestrów, by utworzyć dolne półsłowa wyniku. Rozkazy te są przydatne w zastosowaniach dotyczących przetwarzania obrazu, podobnie jak rozkazy MMX w architekturze x86.
- Rozkazy rozszerzania: Istnieje kilka rozkazów rozpakowywania danych według znaku lub zera, rozszerzających bajty do półsłów lub słów oraz półsłowa do słów.
- Rozkazy dostępu do rejestru stanu: ARM zapewnia możliwość odczytu, a także zapisu części rejestru stanu.
KODY WARUNKOWE. Architektura ARM definiuje cztery znaczniki warunkowe, które są przechowywane w rejestrze stanu programu: N, Z, C i V (Negative, Zero, Carry i Overflow), o znaczeniu zasadniczo takim samym jak znaczniki S, Z, C i V w architekturze x86. Te cztery znaczniki stanowią kod warunkowy w architekturze ARM. W tabeli 13.11 pokazano kombinację warunków, dla których zdefiniowano wykonanie warunkowe.
Tabela 13.11. Warunki do wykonania rozkazów warunkowych w architekturze ARM
Kod
Symbol
Testowany warunek
Komentarz
0000
EQ
Z = 1
Równe
0001
NE
Z = 0
Nierówne
0010
CS/HS
C = 1
Przeniesienie ustawione/bez znaku większy lub taki sam
0011
CC/LO
C = 0
Bez przeniesienia/bez znaku, mniejszy
0100
MI
N = 1
Minus/ujemne
0101
PL
N = 0
Plus/dodatnie lub zero
0110
VS
V = 1
Przepełnienie
0111
VC
V = 0
Brak przepełnienia
1000
HI
C = 1 AND Z = 0
Bez znaku, większy
1001
LS
C = 0 OR Z = 1
Bez znaku, mniejszy lub taki sam
1010
GE
N = V
[(N = 1 AND V = 1)
OR (N = 0 AND V = 0)]
Ze znakiem, większy lub równy
1011
LT
N ? V
[(N = 1 AND V = 0)
OR (N = 0 AND V = 1)]
Ze znakiem, mniejszy niż
1100
GT
(Z = 0) AND (N = V)
Ze znakiem, większy niż
1101
LE
(Z = 1) OR (N = V)
Ze znakiem, mniejszy lub równy
1110
AL
-
Zawsze (bezwarunkowo)
1111
-
-
Ten rozkaz może być wykonany tylko bezwarunkowo
Istnieją dwa nietypowe aspekty stosowania kodów warunkowych w architekturze ARM:
1. Wszystkie rozkazy, a nie tylko rozkazy rozgałęzienia, zawierają pole kodu warunkowego, co oznacza, że praktycznie wszystkie rozkazy mogą być wykonywane warunkowo. Dowolna kombinacja ustawień znaczników, z wyjątkiem 1110 lub 1111, w polu kodu warunkowego rozkazu oznacza, że rozkaz zostanie wykonany tylko wtedy, gdy warunek zostanie spełniony.
2. Wszystkie rozkazy dotyczące przetwarzania danych (operacje arytmetyczne i logiczne) zawierają bit S, który określa, czy rozkaz dokonuje aktualizacji znacznika warunkowego.
Użycie wykonania warunkowego i warunkowego ustawienia znaczników warunkowych pomaga w projektowaniu krótszych programów, które wymagają mniej pamięci. Z drugiej strony, wszystkie rozkazy zawierają 4 bity kodu warunkowego, zatem kompromis polegający na tym, że mniej bitów w 32-bitowej rozkazie jest dostępnych dla kodu operacji i argumentów. Ponieważ architektura ARM jest projektem RISC, który w dużej mierze bazuje na adresowaniu rejestrów, wydaje się to rozsądnym kompromisem.