RÓŻNE ODMIANY ASEMBLERA

 

Niniejszy dokument ma na celu zasygnalizowania istnienia architektur innych niż procesory Intela klasy 80x86. Krótki opis trzech z nich ma jedynie przybliżyć potencjalne róznice w programowaniu na maszynach w nie wyposażonych, a nie przedstawić kompleksowe wskazówki do tworzenia kodu w asemblerze (które to można znaleźć w zależności od potrzeb w sieci). Ma zwrócić uwagę na wielość rozwiązań, dzięki którym pewne funkcje systemowe muszą być realizowane (bądź mogą być optymalizowane) w specyficzny dla architektury sposób oraz pokazać zasadność rozdzielenia części kodu Linuxa na katalogi dla poszczególnych z nich.

 

W dokumencie zostały przedstawione trzy architektury:

·  ARM

·  Alpha

·  Sparc

 

Na każdą z nich istnieje imlementacja systemu Linux.

ARM

 

  1. Typy procesorów ARM

 

ARM 2
ARM 3
ARMv2

ARM 2 or ARM 3

 

ARMv3

ARM6xx or ARM7xx

 

ARMv4

StrongARM

 

ARMv5

ARM9, XScale, etc

 

Floating Point

Hardware or (usually) software floating point, from ARM 2 (software) to ARM7500FE (mostly hardware). No RISC floating point unit is entirely hardware, it is a balance between basic FP instructions in hardware, and more complex things (like RMF) in software.

 

VFP

Vector Floating Point, in ARMv5 (etc) processors

 

Thumb

16 bit-ised version of ARM as an option in ARMv5 (etc) processors

 

 

  1. Rejestry i tryby procesora

 

Procesor może pracować w jednym z czterech trybów.

Różnica pomiędzu IRQ i FIQ polega głównie na tym, że w obsługę FIQ staramy się jak najszybciej zakończyć. Dlatego ma "wycieniowanych" więcej rejestrów. FIQ nie może wywoływać SWI, ani być przerwanym przez IRQ. (IRQ może być przerwany przez FIQ)

 

Procesor ARM ma właściwie 27 rejestrów, które są udostępniane w odpowiednich warunkach, ale tak na prawdę możemy używać najwyżej 16 na raz.

Tabelka poniższa zestawia "używalność" rejestrów.

User Mode  SVC Mode   IRQ Mode   FIQ Mode  APCS
 
R0 ------- R0 ------- R0 ------- R0        a1
R1 ------- R1 ------- R1 ------- R1        a2
R2 ------- R2 ------- R2 ------- R2        a3
R3 ------- R3 ------- R3 ------- R3        a4
R4 ------- R4 ------- R4 ------- R4        v1
R5 ------- R5 ------- R5 ------- R5        v2
R6 ------- R6 ------- R6 ------- R6        v3
R7 ------- R7 ------- R7 ------- R7        v4
R8 ------- R8 ------- R8         R8_fiq    v5
R9 ------- R9 ------- R9         R9_fiq    v6
R10 ------ R10 ------ R10        R10_fiq   sl
R11 ------ R11 ------ R11        R11_fiq   fp
R12 ------ R12 ------ R12        R12_fiq   ip
R13        R13_svc    R13_irq    R13_fiq   sp
R14        R14_svc    R14_irq    R14_fiq   lr
------------- R15 / PC -------------       pc

 

Kolumna APCS zawiera nazwy używane przez specyficzny mechanizm nazywany ARM Procedure Call Standard (jak sama nazwa wskazuje – standaryzuje on wywoływanie procedur skompilowanych, napisanych w różnych językach)

 

Rejest R15 zbudowany jest następująco (tryb 26-bitowy)

 

  Bit  31  30  29  28  27  26  25------------2  1  0
       N   Z   C   V   I   F   Program Counter  S1 S0

 

Oznaczenie flag:
  N  Negative - Ustawiany gdy wynik operacji jest negatywny
  Z  Zero - Ustawiany gdy wynik operacji jest zero
  C  Carry - Ustawiany gdy wystąpi "carry"
  O  Overflow - Ustawiany gdy wystąpi przepełnienie
  I  IRQ - Zablokowanie przerwań
  F  FIQ - Zablokowanie szybkich przerwań  
  
  S1 S0   Tryb
  0    0    USR - User mode
  0    1    FIQ - Fast Interrupt mode
  1    0    IRQ - Interrupt mode
  1    1    SVC - Supervisor mode
 
Jeżeli R15 jest używany jako pierwszy argument instrukcji, zwykle tylko część przechowująca adres jest wykorzystywana. Jednakże instrukcja poniższa spowoduje skopiowanie zawartości PC do R0 i dodanie do nie 256.

 

  ADD    R0, R15, #256

Jeżeli jest używany jako drugi argument, dostępne są wszystkie 32 bity. Poniższy kod pokazuje rozpoznawanie trybu procesora.

   MOV     R0, #3          ; Load a bit mask (%11) into R0
   AND     R0, R0, PC      ; AND R15 into R0, to get the mode status
   CMP     R0, #3          ; Compare mode with '3' (SVC)
   BEQ     svc             ; If SVC mode, branch to 'svc'
   CMP     R0, #2          ; Compare mode with '2' (IRQ)
   BEQ     irq             ; If IRQ mode, branch to 'irq'
   CMP     R0, #1          ; Compare mode with '1' (FIQ)
   BEQ     fiq             ; If FIQ mode, branch to 'fiq'
   CMP     R0, #0          ; Compare mode with '0' (USR)
   BEQ     usr             ; If USR mode, branch to 'usr'

 

To może być także używane do zmiany trybu procesora. NP. na SVC.

   MOV     R6, PC          ; Store original state of PC in R6
   ORR     R7, R6, #3      ; Set SVC mode
   TEQP    R7, #0          ; Write mode flags (in R7) to PC

 

I żeby wrócić do poprzedniego stanu.

 

   TEQP    R6, #0          ; Write previous mode flags (in R6) to PC

 

  1. Tryb 32-bitowy

 

Jednakże procesory klasy powyżej ARM 3 udostępniają możliwość adresowania 32-bitowego poprzez wyrzucenie PSR (flag procesora) z R15 tak by cały mógł być zajmowany przez adres. Co prawda RISC OS zwykle pracuje w trybie 26-bitowym (z wyjątkiem kilku przypadków), ale uzycie trybu 32 wydaje się być konieczne gdy zainstalowane jest więcej ni 28Mb pamięci.

PSR znajduję się wtedy w specjalnym rejestrze CPSR (oraz SPSR gdzie jest zachowany). Jego budowę przedstawia poniższa tabelka:

 

  31 30 29 28  ---   7   6   -   4   3   2   1   0
  N  Z  C  V         I   F       M4  M3  M2  M1  M0
 
                                 0   0   0   0   0     User26 mode
                                 0   0   0   0   1     FIQ26 mode
                                 0   0   0   1   0     IRQ26 mode
                                 0   0   0   1   1     SVC26 mode
                                 1   0   0   0   0     User mode
                                 1   0   0   0   1     FIQ mode
                                 1   0   0   1   0     IRQ mode
                                 1   0   0   1   1     SVC mode
                                 1   0   1   1   1     ABT mode
                                 1   1   0   1   1     UND mode
 
Zwykle procesor będzie używał trybów User26, SVC26, IRQ26 i FIQ26 i chociaż możliwe jest wejście w tryb 32 należy robić to bardzo ostrożnie ponieważ RISC OS może nie być na to przygotowany!

 

  1. SWI

 

SWI jak już było wspomniane to Software Interrupt. W RISC OS SWI są używane do wywoływania procedur systemowych lub modułów (napisanych poza sysemem). Aplikacje zwykle używają modułów by umożliwić nieskopoziomowy zewnętrzy dostęp dla innych aplikacji.

 

Przykładami SWI są:

Używanie SWI w ten sposób umożliwia systemowi budowę modularną, co znaczy, że cały kod podzielony jest na wiele mniejszych wyspecjalizowancyh części.

Kiedy nadejdzie żądanie wykonania odpowiedniego SWI, SWI handler odnajduje pozycję procedury i wywołuje ją przekazując dowolne dane. Wywołanie może wyglądać następująco:

   SWI &02

jak i

   SWI "OS_Write0"

 

  1. Pamięć

 

Komputery z RISC OS pracują na dwóch rodzajach adresów – logicznych i fizycznych. Pamięć logiczna to ta widziana przez OS i programistę, jako używana. Każda aplikacja zaczyna się od adresu &8000 i zajmuje pamięć powyżej niego. Pamięć fizyczna jest tą faktycznie zainstalowaną w maszynie.

W RISC OS pamięć jest podzielona na strony. W starszych wersjach były one rozmiarów 8/16/32K w zależności od zainstalowanej pamięci, a teraz uzywa się stałej wartości 4K. I co najciekawsze w nowych systemach działających na ARM6 i młodszych MMU (Memory Menagment Unit) jest wbudowany w procesor. Zawiera on TLB (Translation Look-aside buffer - zawiera 64 przetłumaczone adresy) oraz mechanizmy kontroli dostępu (16 zdefiniowanych "domen" z wyspecyfikowanymi prawami dostępu) i przechodzenia tablicy translacji (tłumaczenie adresu wirtualnego na fizyczny).

Co ciekawe, zarówno kod i jaki dane (z małymi wyjątkami oczywiście) kompilator próbuje zawsze wyrównać do adresów 32-bitowych, tak aby ułatwić dekompilację dowolnego fragmentu pamięci (instrukcje mają 32-bitowe kody).

6.      Kooperatywna wielozadaniowść

Schemat wielozadaniowości zastosowany w RISC OS jest relatywnie prosty i jasny. Aplikacja która otrzyma dostęp do procesora urzywa go tak długo jak chce, aż do zakończenia lub jawnej rezygnacji na rzecz systemu operacyjnego. Istnieją jednak implementacje (nie tylko Linux), które potrafią symulować inne modele kooperacji.

 

Informacje i przykłady zostały zaczerpnięte ze strony:

http://www.heyrick.co.uk/


ALPHA

 

1    Rejestry

Systemy Alpha posiadają dwa zasadniczo różniące sie typy rejestrów:

·         Integer registers

·         Floating-point registers

Asemblerowych instrukcjie mają jednoznacznie określone jakiego typu rejestów oczekują jako argumentów i wymienianie ich nie jest możliwe.

1.1    Integer Registers

Jest to zestaw 32-óch 64-bitowych rejestrów o nazwach $0 - $31 (chociaż właściwie używane może być tylko 31, bo $31 zawsze ma wartość 0). Niektóre z nich są czasem odpowiednikami rejestrów "generalnych" występujących w innych architekturach. Po dołączeniu odpowiedniej biblioteki (#include <alpha/regdef.h>) można oprócz nazw numerycznych używać specyficznych alisów dla rejestrów (oprócz $28, $29, $30, którye system operacyjny i asembler rezerwuje dla swoich celów). Rejestry można stosować praktycznie dowolnie wymiennie oprócz $30, który jest uzywany jako wskaźnik stosu przez specyficzne instrukcje PALcode (Privileged Architecture Library code). Poniższa tabela opisuje zwykłe znaczenie odpowiednich rejestrów.

 

Nazwa

Nazwa programowa ( regdef.h)

Używany jako

$0

v0

Przechowywanie obliczeń wyrażeń i wartości funkcji. Nie jest zachowywany przy wywoływaniu procedury.

$1-8

t0-t7

Tymczasowe wartości obliczanych wyrażeń. Nie są zachowywane przy wywoływaniu procedury.

$9-14

s0-s5

Rejestry zachowywane przy wywoływaniu procedur.

$15 or $fp

s6 or fp

W razie potrzeby zawiera "frame pointer". Jeśli nie traktowany jest jak rejestr zachowywany.

$16-21

a0-a5

Używane do przekazywania pierwszych 6-ciu argumentów do wywoływanych procedur. Nie są zachowywane.

$22-25

t8-t11

Tymczasowe wartości obliczanych wyrażeń. Nie są zachowywane przy wywoływaniu procedury.

$26

ra

Adres powrotu (return address). Oczywiście zachowywany.

$27

pv or t12

Zawiera wartość procedury (procedure value), a także używany do wyliczania wyrażeń. Nie zachowywany.

$28 or $at

AT

Zarezerwowany dla asemblera. Nie zachowywany.

$29 or $gp

gp

Wskaźnik globalny (global pointer). Nie zachowywany.

$30 or $sp

sp

Wskaźnik stosu (stack pointer). Zachowywany.

$31

zero

Zawsze ma wartość 0.

 

 

1.2    Floating-Point Registers

Tych także mamy 32 64-bitowe rejestry. Każdy z nich może przechowywać liczbę zmiennoprzecinkową pojedyńczej (32 bity) lub podwójnej (64-bity) precyzji. Są nawywane kolejno od $f0 to $f31 (z czego znów ostatni nie jest używany, bo zawsze musi mieć wartość 0.0). Poniżej opis znaczenia poszczególnych z nich.

 

Nazwa

Używany jako

$f0-f1

Przechowywanie obliczeń wyrażeń i wartości funkcji zmienno przecinkowej ($f0) albo zespolone ($f0 część rzeczywista, $f1 część urojona). Nie jest zachowywany przy wywoływaniu procedury.

$f2-f9

Rejestry zachowywane przy wywoływaniu procedur.

$f10-f15

Tymczasowe wartości obliczanych wyrażeń. Nie są zachowywane przy wywoływaniu procedury.

$f16-f21

Używane do przekazywania pierwszych 6-ciu argumentów (zmiennoprzecinkowych pojedyńczej lub podwójnej precyzji) do wywoływanych procedur. Nie są zachowywane.

$f22-f30

Tymczasowe wartości obliczanych wyrażeń. Nie są zachowywane przy wywoływaniu procedury.

$f31

Zawsze ma wartość 0.0.

 

 

2.      Kolejność bitów i bajtów

Kolejność bitów i bajtów jest przedstawiona na poniższym diagramie. W systemie stosowany jest schemat little-endian, czyli bit znaku jest zawsze na miejscu najbardziej znaczącego bitu liczby.

 

Ze względu na różny rozmiar zmiennych i możliwość różnego rozmieszczenia ich w pamięci używa się dwóch typów operacji ładowania i składowania rejestrów w pamięci:

  Aligned Data Operations

·         Load quadword (ldq)

·         Store quadword (stq)

·         Load longword (ldl)

·         Store longword (stl)

·         Load word (ldw)

·         Store word (stw)

·         Load word unsigned (ldwu)

  Unaligned Data Operations

·         Unaligned load quadword (uldq)

·         Unaligned store quadword (ustq)

·         Unaligned load longword (uldl)

·         Unaligned store longword (ustl)

·         Unaligned load word (uldw)

·         Unaligned store word (ustw)

·         Unaligned load word unsigned (uldwu)

·         Load byte (ldb)

·         Store byte (stb)

·         Load byte unsigned (ldbu)

3.      Wyjątki

System Alpha generuje niektóre wyjątki bezpośrednio, a niktóre jako wynik pewnych testów wpisanych w aseblerze. Poniżej opisane są cześciej występujące

3.1  Main Processor Exceptions

·         Address error exceptions – występuje gdy podany adres jest nieprawidłowy w kontekście aktualnego procesu, zwykle gdy wystąpi odwołanie do niedopasowancyh danych (unproperly aligned)

·         Overflow exceptions – brak miejsca na zapisanie precyzji wyniku operacji arytmetycznej.

·         Bus exceptions – nieprawidłowy adres pamięci.

·          Divide-by-zero exceptions – dzielenie przez zero.

3.2    Floating-Point Processor Exceptions

·         Invalid operation exceptions:

o        Odejmowanie nieskończoności, np. (+INF) - (+INF).

o        Mnożenie 0 przez +/-INF.

o        Dzielenie 0 przez 0 lub +/-INF przez +/-INF.

o        Zmiana liczby na format integer (cvttq) powoduje przepełnienie, albo argumentem była nieskończoność lub NaN (not-a-number)

o        Operacje sygnalizujące wynik NaN.

·         Divide-by-zero exceptions – dzielenie przez zero.

·         Overflow exceptions – wynik przekracza możliwości formatu

·         Underflow exceptions – wynik traci dokładność, albo jest pomiędzi +/-2Emin (minimum expressible exponent).

·         Inexact exceptions – wynik z nieskończoną precyzją różni się od zaokrąglonego.

Informacje zaczerpnięte ze strony:

http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V51A_HTML/ARH9LBTE/TITLE.HTM

 


Sparc

 

1. Okna rejestrów

 

Architektura Sparc firmy Sun Microsystem ma 32 rejestry ogólnego zastosowania widoczne dla programu przez cały czas. 8 z nich stanowi rejestry globalne (global) a pozostałe 24 tworzą okna (windows). Rejestry okien podzielone są na trzy grupy po 8 rejestrów nazywane wejściowe, lokalne i wyjściowe (in, local, out). W zależności od implementacji (rodzaju chip'a) liczba okien wynosi od 2 do 32 (zwykle 7 lub 8).

W danym momencie tylko jedno okno jest widzialne (dostępne) na co wskazuje CWP (current window pointer), który jest częścią PSR (processor status register). Jest to pięciobitowa liczba, która jest zwiększana i zmniejszana przez intrukcje SAVE i RESTORE. Są to intrukcje wywłania i powrotu z procedury. Ideą jest by rejestry wejściowe zawierały parametry wejściowe dla procedury, a rejestry wyjściowe przekazywały wynik. Lokalne mają być wykorzystwywane do wewętrznych obliczeń, a globalne do trzymania danych na zewnątrz wywołań. Rejestry okna nakładają się w ten sposób by wyjściowy po wykonaniu SAVE stał się wejściowym, po to aby w ciągu wywołań i powrotów zredukować ruch w pamięci.

Idea jednak okazała się niezbyt fortunna. Okazało się, że i tak często występuje konieczność odkładania rejestrów na stos i przywracania ich stanu później. Pomyłka spowodowana była ograniczonymi symulacjami (jedynie dla izolowanych programów i kompilowanych ze słabą optymalizacją. Co więcej nowsze wersje ze względu na wsteczną kompatybilność nie mogą usunąć tej cechy, co przyspożyło producentom wielu problemów przy projektowaniu nowocześniejszych wersji procesora takich jak SuperSparc).

 

Grupa rejestrów

Mnemoni

Adresy rejestrów

global

%g0-%g7

r[0]-r[7]

out

%o0-%o7

r[8]-r[15]

local

%l0-%l7

r[16]-r[23]

in

%i0-%i7

r[24]-r[31]

 

Na poniższy rysunku została zilustrowana idea nakładania się rejestrów w implementacji z 8 oknami. Okna są tu ponumerowane od 0 do 7 (w0-w7). Składją się z 24 rejestrów z czego 16 dzielonych jest pomiędzy sąsiednie okna. Zwykły przypadek zmiany aktywnego okna to wywołanie SAVE lub RESTORE. Rzadziej RETT (return from trap) lub obsługa zdarzenia pułapki (przerwanie, wyjątek lub intrukcja TRAP).

 

Na rysunku również zaznaczony jest rejestr WIM (window invalid mask). Jest to mapa bitowa z zaznaczonym zawsze tylko jednym bitem pokazującym, które okno jest pierwszym zajętym. Okna rejestrów zostały wymyślone do wpomogania wywoływania procedur czyli są takim jakby cache'wym stosem. WIM wskazuje ile jeszcze wywołań może być jeszcze wykonanych bez zapisywania danych do pamięci. Na rysunku cała pojemność okien jest już wykorzystana i następne wywołanie procedury spowoduje wywołanie "pułapki" Window Overflow. Z drugiej strony występuje Window Underflow, kiedy "cache" jest pusty i trzeba pobrać dane z pamięci.

2. Znaczenie rejestrów

Oprócz tego Sparc zawiera rekomendację co do programowego używania rejestrów zapisaną w dokumentacji Sparc ABI (application binary interface) a także w wielu innych miejscach (jak np. pliki nagłówkow, dokumentacja kompilatora).

Poniżej znajdue się tabelka opisująca typową zawartość rejestrwów:

                 %g0  (r00)       always zero
                 %g1  (r01)  [1]  temporary value
                 %g2  (r02)  [2]  global 2
     global      %g3  (r03)  [2]  global 3
                 %g4  (r04)  [2]  global 4
                 %g5  (r05)       reserved for SPARC ABI
                 %g6  (r06)       reserved for SPARC ABI
                 %g7  (r07)       reserved for SPARC ABI
 
                 %o0  (r08)  [3]  outgoing parameter 0 / return value from callee   
                 %o1  (r09)  [1]  outgoing parameter 1
                 %o2  (r10)  [1]  outgoing parameter 2
     out         %o3  (r11)  [1]  outgoing parameter 3
                 %o4  (r12)  [1]  outgoing parameter 4
                 %o5  (r13)  [1]  outgoing parameter 5
            %sp, %o6  (r14)  [1]  stack pointer
                 %o7  (r15)  [1]  temporary value / address of CALL instruction
 
                 %l0  (r16)  [3]  local 0
                 %l1  (r17)  [3]  local 1
                 %l2  (r18)  [3]  local 2
     local       %l3  (r19)  [3]  local 3
                 %l4  (r20)  [3]  local 4
                 %l5  (r21)  [3]  local 5
                 %l6  (r22)  [3]  local 6
                 %l7  (r23)  [3]  local 7
 
                 %i0  (r24)  [3]  incoming parameter 0 / return value to caller
                 %i1  (r25)  [3]  incoming parameter 1
                 %i2  (r26)  [3]  incoming parameter 2
     in          %i3  (r27)  [3]  incoming parameter 3
                 %i4  (r28)  [3]  incoming parameter 4
                 %i5  (r29)  [3]  incoming parameter 5
            %fp, %i6  (r30)  [3]  frame pointer
                 %i7  (r31)  [3]  return address - 8
 
 
[1] wywołujący zakłada, że wartość się zmieni podczas wykonywania procedury
[2] nie powinien być wykorzystywany w bibliotekach SPARC ABI
[3] wywołujący zakłada, że wartość zostanie zachowana podczas wykonywania procedury

 

Informacje zaczerpnięte ze strony:

http://www.sics.se/~psm/sparcstack.html