EMAS

Z MERA 400 wiki
Przejdź do nawigacji Przejdź do wyszukiwania

EMAS jest assemblerem skrośnym dla MERRY 400, używającym, w odróżnieniu od ASSEM, składni bliższej współczesnym assemblerom, oraz mnemoników MERY 400 (ale bez przyrostków zależnych od lokalizacji argumentu, patrz: Lista rozkazów).

Repozytorium źródeł EMAS: 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.
    • -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)

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, np:

0b1101110000
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. Trójznaki:

'abc', 'XYZ', '#_1'

kodowane są w słowie za pomocą kodu R40.

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

Dostępność symboli na zewnątrz obiektu

Wszystkie symbole definiowane są jako lokalne dla 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.

.lbyte

Składnia: .lbyte liczba_8_bit [, ...]

Zapisuje w programie wynikowym na lewych pozycjach (MSB) kolejnych słów podane wartośći 8-bit.

.rbyte

Składnia: .rbyte liczba_8_bit [, ...]

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'.

.r40

Składnia: .r40 łańcuch_r40

Zapisuje w programie wynikowym podany łańcuch, kodując go po trzy znaki R40 na słowo maszynowe. UWAGA: W kodzie R40 można użyuwać jedynie liter, liczb i znaków specjalnych: _, #, %.

.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 etykieta będzie miała zasięg globalny.

.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 r2 + 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 r2 + struktura.dluga_liczba
lw r7, moja_str + struktura.tekst

Rozkazy

Patrz: Lista rozkazów).