Assembler HLA (II)

Ten przedmiot to jednak programowanie niskopoziomowe. Dlatego też użyjmy HLA jako wsparcia we wczytywaniu i wypisywaniu danych na standardowe wyjście, ale używajmy go jak asemblera.

Przekazywanie wartości przez rejestry

Wiemy, że można wywoływać procedury wraz z listą argumentów. My jednak wolimy używać stosu lub rejestrów do przekazywania argumentów do procedury.

Np. kod:

program cube;

procedure objetoscSzescianu;

begin objetoscSzescianu;

mul(ebx,eax);

mul(ecx,eax);

end objetoscSzescianu;

begin cube;

mov(2,ecx);

mov(3,ebx);

mov(4,eax);

call objetoscSzescianu;

// eax ma naszą objętość czyli wynik 2 x 3 x 4

end cube;

wywoła procedurę o nazwie objetoscSzescianu, która pomnoży zawartość rejestru ebx z zawartością eax, a następnie wynik zapisany w eax pomnoży jeszcze przez ecx i zapisze w eax.

Dzięki temu iż w przekazaliśmy wartości do tych rejestrów procedura wykona operacje na tych wartościach. Po zakończeniu procedury (tak jak napotkaniu ret w NASMie) wracamy do miejsca, w którym została wywołana procedura. I w tym momencie eax zawiera nasz wynik z obliczeń dokonanych w procedurze.

Skorzystajmy jednak z biblioteki stdlib.hhf i napiszmy program, który będzie pobierał z klawiatury parametry do procedury i wyświetlał wynik jej działania.

program cube;

#include("stdlib.hhf");

procedure objetoscSzescianu;

begin objetoscSzescianu;

mul(ebx,eax);

mul(ecx,eax);

end objetoscSzescianu;

begin cube;

stdin.geti32();

mov(eax,ecx);

stdin.geti32();

mov(eax,ebx);

stdin.geti32();

stdout.puti32(ecx);

stdout.put(" x ");

stdout.puti32(ebx);

stdout.put(" x ");

stdout.puti32(eax);

stdout.put(" = ");

call objetoscSzescianu;

stdout.puti32(eax);

stdout.newln();

end cube;

Przekazywanie wartości przez stos

Co jednak jeśli mamy więcej danych niż rejestrów, lub z innych powodów nie chcemy korzystać z rejestrów podczas przekazywania danych do procedury (np. boimy się, że rejestry mogą zmienić swoją zawartość nim procedura odczyta ich zawartość.

Skorzystajmy więc ze stosu:

program cube2_0;

#include("stdlib.hhf");

procedure objetoscSzescianu;

begin objetoscSzescianu; end objetoscSzescianu;

begin cube2_0; end cube2_0;

Po wrzuceniu danych na stos znajdują się one na jego szczycie. Jednak po wywołaniu procedury, na szczyt stosu wrzucane są dane dotyczące adresu powrotnego z procedury bazy stosu itp. W sumie wrzucane są 4 słowa. Dlatego też przy odczytywaniu tych danych musimy czytać z przesunięciem o 4*4 (16). ESP trzyma aktualny wskaźnik na szczyt stosu. Dlatego gdy chcemy odczytać wartość wrzuconą na stos bezpośrednio przed wywołaniem procedury to piszemymov([esp+16],destination); i odpowiednio dla kolejnych wcześniej wrzucanych wartości zwiększając adres o kolejne 4.

Jako, że mamy do czynienia ze stosem wyciągamy dane w odwrotnej kolejności niż je wkładaliśmy.

Np jeśli wrzucaliśmy na stos n wartości a chcemy się dobrać do wartości i to piszemy:

mov([esp+16+(n-i)*4],destination);

Zwracanie wartości przez stos nie ma większego sensu, gdyż po powrocie z procedury te 4 wrzucone prze jej wywołaniem słowa są ściągane i wskaźnik stosu zmniejszany do wartości z przed wywołania procedury. Oznacza to iż pierwsza wartość wrzucona, podczas wykonywania procedury, na stos była pod adresem[esp-20], a kolejne odpowiednio o x*4 dalej. Jednakże, skoro szczyt stosu spadł, to znaczy, że tamta pamięć została zwolniona i nie mamy gwarancji na to, iż dane te nie zostały już nadpisane.

Kolejną rzeczą, którą się zajmiemy jest alokowanie pamięci. Przypuśćmy, że chcemy sobie zrobić własny stos dostępny z dowolnej procedury. Moglibyśmy użyć tablicy (i to też pokażemy), ale czasem nie znamy wielkości pamięci, która będzie nam potrzebna.

Alokowanie tablicy

Zacznijmy więc od zaalokowania pamięci dla tablicy o wielkości pobranej z klawiatury.

program tabAlloc;

#include("stdlib.hhf");

procedure fillTab;

begin fillTab; end fillTab;

begin tabAlloc; end tabAlloc;

Spróbuj wejścia:

4

1

2

3

4

I co otrzymasz? Tak ładnie wypisane:

1 2 3 4

HLA Exception (54) at line 777 in ex_InstallSignals.hla, edx=$00000000

Memory Access Violation

Pamięć na objekty

Teraz zajmijmy się naszym stosem. Będziemy alokować pamięć dla obiektu, który będzie wyglądał tak:

program stos;

#include("stdlib.hhf");

#include("excepts.hhf"); // HLA ma nam ulatwiac zycie ;]

static

stosTop:

uns32 := 0;

procedure pushOnStos;

begin pushOnStos;

push(eax);

push(ebx);

malloc(8);

mov(eax,ebx);

mov([esp+24],eax);

mov(eax,[ebx]);

mov(stosTop,[ebx+4]);

mov(ebx, stosTop);

mov([ebx],eax);

pop(ebx);

pop(eax);

end pushOnStos;

procedure popFromStos;

begin popFromStos;

push(ebx);

mov(stosTop,ebx);

mov([ebx],eax);

mov([ebx+4],stosTop);

pop(ebx);

end popFromStos;

procedure isEmptyStos;

begin isEmptyStos;

xor(al,al);

cmp(stosTop,0);

jnz falsz;

mov(1,al);

falsz:

end isEmptyStos;

begin stos; end stos;

W taki sposób możemy sobie bardzo łatwo tworzyć własne obiekty w asemblerze. Tu używaliśmy HLA, aby pominąć zabawę z wczytywaniem z klawiatury, sprawdzaniem czy wpisano liczbę oraz wypisywaniem. Można to jednak zrobić w analogiczny sposób w NASMie.

Zadania

Dziś jedno, za to trochę trudniejsze:

Napisz quickSorta, ale w taki sposób by oszczędzać zużycie pamięci.

    • 0) pobierz dane do tablicy (n liczb typu int32 pobranych z klawiatury), i wyświetl ją,

    • 1) wrzuć na stos granice 0 i n-1,

    • 2) dopóki stos nie jest pusty:

      • (a) ściągnij ze stosu przedział tablicy, na którym będziesz operować,

      • (b) wywołaj na tym przedziale funkcję sortującą*,

      • (c) podziel przedział w miejscu wskazanym przez funkcję sortującą i wrzuć na stos większy przedział, wróć do (a),

    • 3) wyświetl już posortowaną tablicę.

* - jak w zwyczajnym qs:

    • 0) pobierz element wzorcowy ze środka tablicy,

    • 0)* można zrobić jedną modyfikację i wybrać losowy index w zakresie jako element wzorcowy (poprawia szybkość w złośliwych testach),

    • 1) zamień element wzorcowy z lewą granicą (L),

    • 2) zaczynając od indeksu i = L + 1, oraz j = R dopóki i <= j:

      • (a) dopuki element o indeksie i jest mniejszy od wzorca to ++i,

      • (b) dopuki element o indeksie j jest większy od wzorca --j,

      • (c) zamień i z j, zwiększ i, zmniejsz j i wróć do (a),

    • 3) zamień miejscami elementy o indeksach j oraz L,

    • 4) zwróć j.