Ogólny schemat
Ponizej jest czasoprzestrzenny diagram dla jakiegoś obliczenia wg. jakiegoś antionowego programu. Wyróżnilismy w nim wszystkie trzy podstawowe fazy, czyli ekspansję, permutację i retrakcję. Instrukcje tx(), t(), o() nie zależą od punktu, w którym ma być wykonana operacja. Dlatego tx(), t(), o() w górnym wierszu diagramu dotyczą całych kolumn. Kółka w diagramie to symbole operacji. Albo też mozna powiedzieć miejsca dla antionów (placeholders). Kółko naprawdę reprezentuje antion w synchronicznym obliczeniu, jeśli jest zakolorowane. Niebieskie kółka to są „teraźniejsze” antiony. Czas przeszły jest w lewo od teraźniejszości, czas przyszły jest w prawo.
Synchroniczna interpretacja antionowego programu polega na tym, że antiony znajdujące sie w stanie teraźniejszym Sτ, czyli niebieskie antiony, są naraz odpalone i przechodzą do stanu Sτ+1 , czyli do kolumny w prawo. Asynchroniczna implementacja nr.16 programu na jednym procesorze polega na serializacji takiego równoczesnego odpalania. Sposób implementacji jest przedstawiony na nastepującym rysunku.
Do antionowego programu prob-16-main mamy doczepiony kernel prob-16-kern. W kernelu są definicje funkcji tx(), t() i o(). Z prawej strony kernelu jest przyczepiona kolejka antionów. Kolejka zawiera antiony z dwu sąsiednich kolumn czasoprzestrzennego diagramu: teraźniejszego Sτ i przyszłego Sτ+1.*)
*) Żeby nie komplikować opisu, koncentrujemy się dalej na antionach i opuszczamy szczegóły co robi kernel z danymi punktowymi.
Jak to działa? Antionowy program prob-16-main wykonuje jakieś operacje na swych danych nad i pod „main”. To sa dane „teraz”, w aktualnym punkcie i w aktualnym antionie. Kiedy w przyjdzie w programie kolej na operację tx(), t(), lub o(), control przechodzi do kernelu (przejdzie do definicji tych operacji). Funkcja w kernelu bierze antion z prob-16-main (czyli bierze strukturę, gdzie jest wartość punktu i aktualny programowy stos), wykona nad nim wymaganą przestrzenną operację t(), tx(), lub o(), i przesuwa wynikową strukturę (jeden antion, albo dwa, albo żaden) na koniec kolejki. Prawa połowa kolejki, jak ją widzimy na rysunku na czerwono, są więc antiony z kolumny Sτ+1, antiony (stosy) przygotowane dla czasu przyszłego. Następnie kernel bierze nowy niebieski antion z kolejki (z kolumny Sτ), i umieszcza go do lokalnych struktur programu prob-16-main (do lokalnego programowego stosu). Ostatnia operacja kernelu to „return”. Co jest bardzo ważne jest to, że adres returnu jest na szczycie stosu. Cykl się zamyka, jesteśmy znowu w programie prob-16-main, w miejscu bezposrednio za poprzednią operacją tx(), t(), lub o().
Jeśli w kolejce wszystkie niebieskie elementy zostaną wyczerpane, zmieniamy kolor czerwonych elementów (jesli sa jakieś) na niebieski i powtarzamy od miejsca „jak to działa„.
Dane w kernelu
Antion ma w kernelu nastepującą strukturę:
typedef struct {
int p; // punkt przestrzeni
unsigned sp, bp; // zmienne pomocnicze, wartosci rejestrów
unsigned stack[SIZEOFSTACK]; // stos
} antstr;
Słowami: antion jest jednak punkt p, w którym sie znajduje, oraz stos, gdzie są dane lokalne i ważne rejestry, w tym program counter. Kernel włoży zawartość „teraźniejszego” programowego stosu, czyli lokalnych danych w prob-16-main, do stack[SIZEOFSTACK]. Zmodyfikuje zmienną „p”. Przeniesie „terazniejsze” punktowe dane do pola punktów space [p]. Zrówna wartości „currentpoint” i „p”. Przemieszcza antion (strukturę antstr, w którym najwazniejszą role pełni stack) na koniec kolejki „antqueue”
antstr antqueue[MAXQUEUE];
W przypadku o() antion znika. Następnie kernel pobiera dalszy antion z kolejki, umieszcza go jako „terazniejsze” lokalne programowe dane do prob-16-main, i wraca control do tego programu operacją „return”.
Zmienne sp, bp są pomocnicze, służą do zapamietania wartosci rejestrów ESP i EBP (stack pointer, base pointer). Zmienna „p” okresla punkt, w którym sie antion aktualnie znajduje. Jest to indeks do pola
void* space[SPACESIZE];
Równocześnie to jest element przestrzeni (C2 )n . Pole „space” zawiera pointery na kawałki pamięci, które kernel dynamicznie alokuje funkcją „malloc” systemu Windows. Ile pamieci trzeba alokować jest określone wartością zmiennej „extern int sizeofpoint”. Jest tam „extern”, poniewaz faktycznie ta wartość jest definiowana w programie „main()”, na zewnątrz kernelu. Jeśli przykładowo w antionowym programie „main()” deklarujemy
int pointdata; // point variable (4)
int a; // point variable (4)
int x; // point variable (4)
int u; // point variable (4)
to równocześnie trzeba deklarować
int sizeofpoint = 16; // summary length of point variables
żeby w kernelu było wiadomo jak duzo trzeba alokować. Kompilator „C++” umieści zmienne pod „pointdata” ściśle za sobą, dzięki czemu punktowe dane tworzą jeden pamieciowy blok. Nazwa pierwszej zmiennej w bloku, czyli „pointdata” jest obowiązkowa. Kernel musi wiedziec, w którym miejscu zaczyna blok punktowych danych. A więc pamietajcie o deklaracji „pointdata” i o „sizeofpoint” w antionowych programach !! I jeszcze jedna uwaga: w bloku punktowych danych nie można deklarować stałych, np. w ten sposób: „int a = 1”. To z tego powodu, ze kompilator „Microsoft C++” (w aktualnej wersji i prawdopodobnie we wszystkich następnych wersjach) alokuje takie zmienne w innej części pamięci komputera. Blok punktowych danych „pointdata” byłby wtedy niezupełny, czyli do niczego.
Funkcje kernelu
Ruch w przestrzeni załatwia kernel, do którego sie zwracamy poprzez funkcje tx(r), t(r), o(). Zobaczmy jak to wygląda w „prob-16-kern.cpp” .
….