EMAS
EMAS jest assemblerem skrośnym dla komputera MERA 400, używającym (w odróżnieniu od ASSEM i ASSM) składni bliższej współczesnym assemblerom.
Repozytorium źródeł EMAS-a dostępne jest pod adresem: https://github.com/jakubfi/emas
Wywołanie
Wywołanie EMAS z linii poleceń ma postać:
emas [opcje] [wejście]
Gdzie:
- wejście - nazwa wejściowego pliku źródłowego. Jeśli nie zostanie podana, EMAS czyta program ze standardowego wejścia
- opcje - dowolne z:
- -O <otype> - typ wyniku asemblacji. Możliwe wybory to:
- raw - binarny obraz gotowy do umieszczenia w pamięci maszyny i uruchomienia (domyślny wybór)
- emelf - konsolidowalny obiekt EMELF
- debug - tekstowy opis zawartości kolejnych komórek pamięci
- keys - tekstowy opis zawartości kolejnych komórek pamięci w formacie łatwym do wprowadzenia z pulpitu technicznego maszyny
- -o <wyjście> - nazwa pliku wyjściowego. Jeśli nie zostanie podana, domyślny wygór zależy od typu wyniku asemblacji:
- raw - nazwa pliku wejściowego z pominiętym rozszerzeniem. Jeśli wejście nie jest plikiem, to wyjściem jest plik 'a.out'
- emelf - nazwa pliku wejściowego z rozszerzeniem zamienionym na '.o'. Jeśli wejście nie jest plikiem, to wyjściem jest plik 'a.out'
- debug i keys - standardowe wyjście
- -c <cpu> - wybierz procesor, dla jakiego assembler produkuje binaria. Możliwe wybory to: mera400, mx16. Ustawienie procesora na MX-16 pozwala na użycie dodatkowych instrukcji oraz poszerza przestrzeń adresową programu z 32 do 64k słów. Domyślnie przyjmowany procesor to mera400. Opcja nadpisuje wybór dokonany w źródłe dyrektywą .cpu.
- -I <katalog> - dodaj katalog do listy katalogów, w których poszukiwane będą pliki dołączane dyrektywą .include
- -D <stała>[=wartość] - zdefiniuj stałą i opcjonalnie nadaj jej wartość
- -v - wyświetl informacje o wersji i zakończ działanie
- -h - wyświetl pomoc i zakończ działanie
- -d - drukuj informacje przydatne w śledzeniu błędów działania asemblera (dla deweloperów)
- -O <otype> - typ wyniku asemblacji. Możliwe wybory to:
Oprócz katalogów podanych jako argumenty opcji '-I' EMAS używa następujących domyślnych katalogów, w których poszukuje plików dołączanych dyrektywą .include" (w kolejności):
- bieżący katalog
- /usr/share/emas/include
- /usr/local/share/emas/include
Opis języka
Komentarze
EMAS pozwala na użycie komentarzy jedo- i wieloliniowych:
; samotny komentarz jednoliniowy lw r1, 2 ; komentarz jednoliniowy na końcu dowolnej linii /* komentarz wieloniniowy */ lw r1, 2 /* komentarz wieloliniowy, choć tak naprawdę w jedej linii */ /* można też tak */ lw r1, 2
Stałe
Stałymi w programie mogą być liczby całkowite, liczby zmiennoprzecinkowe oraz łańcuchy znaków.
Liczby całkowite
Liczby całkowite można zapisywać w systemie binarnym, ósemkowym, dziesiętnym lub szesnastkowym. Dozwolone jest też rozdzielanie cyfr znakiem podkreślenia.
0b1101110000 0b_1111_0000_1100_0011 0177 -931 0xffa9
Istnieje jeszcze jeden sposób zapisu stałych całkowitoliczbowych, wynikający z ułożenia wskaźników w rejestrze R0, gdzie poszczególnym bitom przypisany jest jednoznakowy symbol ze zbioru (Z, M, V, C, L, E, G, Y, X, 1, 2, 3, 4, 5, 6, 7). Można dzięki niemu zapisać liczbę szesnastobitową z ustawionymi na 1 bitami, które odpowiadają danym wskaźnikom. Przykłady takiego zapisu stałych:
?ZM125 ?GVZ
Wewnętrznie EMAS operuje na 32-bit liczbach ze znakiem. Kiedy ostateczna wartość będąca wynikiem operacji ma zostać umieszczona w obiekcie wynikowym, jej zakres jest sprawdzany i użytkownik jest informowany o ewentualnym jego przekroczeniu. Wyjątkiem jest argument dyrektywy .dword, który jest 32-bit liczbą w kodzie U2.
Znaki
EMAS pozwala na zapisywanie stałych całkowitoliczbowych również przy pomocy znaków, dwuznaków lub trójznaków. Znaki pojedyncze oraz dwuznaki obejmują cały zakres znaków ASCII i można je podawać również używając sekwencji unikowych. Trójznaki pozwalają kodować jedynie znaki poprawne dla kodu R40.
Znaki w pojedynczych cudzysłowach:
'a', '\n', '\xa1', '\0177'
kodowane są jako ich wartość w kodzie ASCII na młodszym bajcie słowa. Dwuznaki:
'ab', '\r\n', '\xaa\0001'
kodowane są jako wartości podanych znaków w kodzie ASCII odpowiednio na starszym i młodszym bajcie słowa.
Liczby zmiennoprzecinkowe
Akceptowane są następujące sposoby zapisu liczb zmiennoprzecinkowych:
9 1.2 0.003 -9.125e-2 1e4
Jedynym miejscem w programie, w którym może wystąpić liczba zmiennoprzecinkowa, jest argument dyrektywy .float.
Łańcuchy
Łańcuchy znaków są ciągami znaków ujętymi w podwójne cudzysłowy. Poszczególne znaki można zapisywać dosłownie, lub (w przypadku dyrektyw .ascii i .asciiz) używając jednej z sekwencji unikowych:
- \0yyy - gdzie yyy jest ósemkowym kodem znaku
- \xyy - gdzie yy jest szesnastkowym kodem znaku
- \y - umożliwia zakodowanie jednego znaków specjalnych (wartości y inne niż poniżej są niepoprwane):
- \n - nowa linia
- \t - tabulacja pozioma
- \r - powrót karetki
- \f - wysuw strony
- \\ - dosłowny ukośnik w tył
- \" - dosłowny podwójny cudzysłów
Przykłady poprawnych łańcuchów znaków:
"MERA-400\n" "\xab\xac\xad" "\"napis\""
To, w jaki sposób znaki umieszczane są w programie wynikowym zależy od dyrektywy, której łańcuch jest argumentem.
Bieżąca lokalizacja
Specjalną stałą jest "." (kropka), która w danym miejscu programu zawsze zwraca przesunięcie względem początku assemblowanego obiektu. Jej wartość jest zawsze >= 0.
Symbole
Symbolem jest ciąg znaków zaczynający się od małej lub dużej litery, z następującą dowolną ilością małych lub dużych liter, liczb lub znaku podkreślenia. Symbolowi przypisywana jest w trakcie asemblacji wartość oraz atrybuty. Symbol może być:
- niezdefiniowany - wystąpiła deklaracja symbolu, ale nie został on (jeszcze) zdefiniowany,
- lokalny lub globalny - widoczny tylko w granicach obiektu, w którym występuje, bądź również poza nim,
- zmienny lub stały - wartość symbolu może, bądź nie może ulec zmianie w trakcie asemblacji programu.
Symbol otrzymuje atrybuty zgodne z tym w jakim kontekście został zdefiniowany bądź zadeklarowany.
Zmienne assemblera
Zmienne assemblera nie mają swojej reprezentacji w wynikowym obiekcie, są wartościami dostępnymi w trakcie asemblacji programu, a definiuje się je dyrektywą .equ. Symbol, który jest zmienną assemblera jest zawsze zmienny. Przykłady zmiennych:
.equ a 10 .equ b a+2
Stałe assemblera
Stałe assemblera również nie mają swojej reprezentacji w wynikowym obiekcie, są wartościami dostępnymi w trakcie asemblacji programu, a definiuje się je dyrektywą .const. Symbol, który jest zmienną assemblera jest zawsze stały. Przykłady stałych:
.const a 10 .const b a+9
Etykiety
Etykieta opisuje pozycję względem początku obiektu (jest nią wartość licznika rozkazów w danym momencie asemblacji). Przykład definicji etykiety:
etykieta:
Zasięg symboli
EMAS pozwala na zawężenie zasięgu symbolu przez poprzedzenie go kropką. Tak zdefiniowany symbol dostępny jest w zakresie od poprzedniego do następnego symbolu globalnego. Dzięki temu możliwe jest używanie takich samych nazw etykiet lokalnych w różnych kontekstach. Na przykład:
globalny1: ... .lokalny: ... ujs lokalny globalny2: .lokalny: ... ujs lokalny
Skok 'ujs' wykonany zostanie każdorazowo do adresu opisanego symbolem ".lokalny", różnego dla zasięgów "globalny1" i "globalny2". Symbol lokalny nie może być zdefiniowany, jeśli uprzednio nie wystąpił żaden symbol globalny.
Można również odwołać się do lokalnego symbolu spoza jego zasięgu poprzedzając jego nazwę nazwą symbolu globalnego, w kontekście którego został zdefiniowany:
globalny1.lokalny globalny2.lokalny
Lokalnych symboli można używać również definiując stałe i zmienne:
zmienna: .equ a 1 .equ b 2
i odwoływać się do nich:
lw r1, zmienna.a lw r2, zmienna.b
Widoczność symboli dla konsolidatora
Wszystkie symbole definiowane są jako widoczne wyłącznie w ramach asemblowanego obiektu. Jeśli symbol ma być widoczny dla konsolidatora, należy go zadeklarować jako zewnętrzny dyrektywą .global. Zmiana zasięgu może nastąpić przed, lub po definicji:
.global printf .global snprintf .global strdup printf: ... snprintf: ... strdup: procedura: .global procedura
Wyrażenia
Operatory
Poniższa tabela przedstawia operatory arytmetyczne i bitowe, w kolejności malejących priorytetów
Operatory | Działanie |
---|---|
~ | negacja bitowa |
\ | skalowanie |
*, /, % | mnożenie, dzielenie, reszta z dzielenia |
+, - | dodawanie, odejmowanie |
<<, >> | przesunięcie bitowe w lewo i w prawo |
&, |, ^ | logiczny iloczyn, suma i suma wykluczająca |
Operator skalowania realizuje funkcję dokładnie taką samą, jak przesunięcie bitowe, jest tylko innym sposobem jego zapisu. Poniższe zapisy są równoznaczne:
3\7 = 3 << (15-7)
Skalowanie można rozumieć jako: "umieść liczbę 3 od 7 pozycji bitowej w górę. Bity dla tego zapisu numerowane są od 0 (bit najstarszy) do 15 (bit najmłodszy). Wynikiem będzie więc liczba:
0000001100000000
Dyrektywy assemblera
.cpu
Składnia: .cpu <mera400|mx16>
Odpowiednik przełącznika -c linii poleceń EMAS-a. Pozwala określić typ procesora, dla którego ma być generowany kod wynikowy. Użycie mx16 sprawia, że:
- mnemoniki CRON, SINT, SIND są rozpoznawane i poprawnie assemblowane
- maksymalny rozmiar wynikowego pliku binarnego wynosi 64k słowa (32k dla mera400)
.file
Składnia: .file nazwa_pliku
Ustala nazwę assemblowanego pliku. Jeśli assembler będzie chciał użyć w komunikatach nazwy pliku, to domyślna nazwa (zgodna z nazwą assemblowanego pliku) zostanie od momentu wydania dyrektywy zastąpiona podaną.
.line
Składnia: .line numer_linii
Ustala numer bieżącej linii assemblacji (poczynając od linii następującej po dyrektywie). Dalsze linie będą numerowane kolejno od podanej wartości.
.include
Składnia: .include nazwa_pliku
Powoduje dołączenie do assemblacji pliku o podanej nazwie.
.equ
Składnia: .equ nazwa_zmiennej wartość
Ustala wartość zmiennej assemblacji. Może być ona w dalszej części programu zmieniana.
.const
Składnia: .const nazwa_zmiennej wartość
Ustala wartość stałej assemblacji. Wartość ta nie może ulec zmianie.
Zapisuje w programie wynikowym na prawych pozycjach (LSB) kolejnych słów podane wartośći 8-bit.
.word
Składnia: .word liczba_16_bit [, ...]
Zapisuje w programie kolejne słowa 16-bit o podanej wartości.
.dword
Składnia: .dword liczba_32_bit [, ...]
Zapisuje w programie wynikowym pary słów 16-bit tworzące podaną liczbę 32-bit.
.float
Składnia: .float liczba_zmiennoprzecinkowa [, ...]
Zapisuje w programie wynikowym trójki słów 16-bit tworzące podaną 48-bit liczbę zmiennoprzecinkową w notacji używanej przez arytmometr wielokrotnej precyzji MERY-400.
.ascii
Składnia: .ascii łańcuch
Zapisuje w programie wynikowym podany łańcuch, kodując go po dwa znaki ASCII na słowo maszynowe.
.asciiz
Składnia: .asciiz łańcuch
Zapisuje w programie wynikowym podany łańcuch, kodując go po dwa znaki ASCII na słowo maszynowe. Łańcuch terminowany jest 8-bit wartością '0'.
.res
Składnia: .res rozmiar [,liczba_16_bit]
Rezerwuje w programie wynikowym obszar o podanym rozmiarze (w słowach), wypełniając go zerami. Jeśli podany jest drugi argument, jego wartość używana jest do wypełnienia zamiast zera.
.org
Składnia: .org adres
Ustala nową bieżącą pozycję assemblacji. Następne wygenerowane słowo zostanie umieszczone już pod podanym, nowym adresem.
.entry
Składnia: .entry adres
Dyrektywa ma efekt wyłącznie w przypadku generowania pliku wynikowego jako obiektu EMELF. Określa adres wejścia do programu.
.global
Składnia: .global nazwa_etykiety
Dyrektywa ma efekt wyłącznie w przypadku generowania pliku wynikowego jako obiektu EMELF. Wskazuje, że symbol będzie widoczny dla linkera.
.ifdef, .ifndef
Dyrektywa pozwala na asemblację warunkową podejmowaną istnieniem (bądź nie) zdefiniowanego symbolu.
.ifdef SYMBOL ... kod asemblowany, jeśl SYMBOL jest zdefiniowany ... .else ... kod asemblowany, jeśli SYMBOL nie jest zdefiniowany ... .endif
.ifndef SYMBOL ... kod asemblowany, jeśli SYMBOL nie jest zdefiniwoany .endif
Symbol określający warunek może być zarówno etykietą, stałą lub zmienną zdefiniowaną w pliku źródłowym, jak również stałą zdefiniowaną podczas wywołania emas-a z opcją '-D'
.struct
Dyrektywa ta pozwala na stworzenie opisu struktury danych w celu uczynienia kodu źródłowego bardziej czytelnym. Nie formalizuje ona struktury, a jedynie ułatwia pracę z danymi. Przykład opisu struktury 'struktura':
.struct struktura: adres .res 1 dluga_liczba .res 2 tekst .res 64 .endstruct
Kolejne pozycje nazywają kolejne pola struktury i określają ich długość w słowach. Po asemblacji symbolom o tych nazwach przypisany zostanie offset w strukturze, a nazwie struktury - jej długość.
Użycie struktury w programie może wyglądać następująco:
moja_str: .res struktura lw r3, moja_str ld r3 + dluga_liczba lw r7, moja_str + tekst
Definicja struktury z powyższego przykładu równoważna jest de facto następującemu ciągowi dyrektyw:
.const adres 0 ; offset pola 'adres' .const dluga_liczba 1 ; offset pola 'dluga_liczba' .const tekst 3 ; offset pola 'tekst' .const struktura 67 ; rozmiar struktury 'struktura'
W strukturach można również zawęzić zakres nazewnictwa pól:
.struct struktura: .adres .res 1 .dluga_liczba .res 2 .tekst .res 64 .endstruct
Wtedy nazwy pól będą dostępne wyłącznie w kontekście danej struktury, a jej użycie wyglądać będzie następująco:
moja_str: .res struktura lw r3, moja_str ld r3 + struktura.dluga_liczba lw r7, moja_str + struktura.tekst
Rozkazy
EMAS używa mnemoników z oryginalnej listy rozkazów MERY-400, ale wprowadza zmiany składniowe wynikające z innego zapisu argumentu normalnego. Nie używa przyrostków zależnych od lokalizacji argumentu. Czyli np. rozkaz ```LWn`` z listy rozkazów, gdzie ```n``` zmienia się w zależności od typu argumentu normalnego, ma zawsze mnemonik ```LW```. Lokalizację argumentu określa składnia dla rejestrów rB i rC. Poniższe przykłady ładowania wartości do rejestru r1 przedstawiają wszystkie możliwe postacie argumentu normalnego:
- LW r1, r2 - ładowana jest wartość z rejestru r2
- LW r1, r2+r3 - ładowana jest wartość z rejestru r2 B-modyfikowana (sumowana z) rejestrem r3
- LW r1, [r2] - ładowana jest wartość z komórki pamięci (D-modyfikacja) wskazanej zawartością rejestru r2
- LW r1, [r2+r3] - ładowana jest wartość z komórki pamięci (D-modyfikacja) wskazanej zawartością rejestru r2 B-modyfikowanego (sumowanego z) rejestrem r3
- LW r1, 0x1000 - ładowana jest wartość 0x1000
- LW r1, 0x1000+r2 - ładowana jest wartość 0x1000 B-modyfikowana zawartością (sumowana z) rejestru r2
- LW r1, [0x1000] - ładowana jest wartość z komórki pamięci (D-modyfikacja) o adresie 0x1000
- LW r1, [x1000+r2] - ładowana jest wartość z komórki pamięci (D-modyfikacja) o adresie 0x1000 B-modyfikowanym zawartością rejestru r2