Virtuelna mašina na bazi steka i virtuelna mašina na bazi registara — u čemu je razlika?
Ovo poglavlje prvobitno je bilo namenjeno direktnom objašnjavanju instrukcija bajtkoda, ali pre toga moramo govoriti o arhitekturi skupa instrukcija, a arhitektura skupa instrukcija deli se na dve vrste: na bazi steka i na bazi registara.
Zato ćemo u ovom poglavlju posebno obraditi virtuelnu mašinu na bazi steka i virtuelnu mašinu na bazi registara, njihove razlike i prednosti i mane svake.
Stek i registar
Strukturu podataka steka već smo ranije obradili; link vodi direktno do nje.
Registar (Register) jeste memorija unutar centralnog procesora (CPU) koja služi za privremeno čuvanje instrukcija, podataka i adresa, ujedno i memorija iz koje CPU najbrže čita i u koju najbrže piše.

Sa hardverske strane, stek se nalazi u memoriji, dok je registar u CPU-u; upravo zato se obično kaže da je virtuelna mašina na bazi registara brža od one na bazi steka.
Virtuelna mašina na bazi steka
Ranije, kada smo obrađivali razvojnu istoriju JDK-a, pomenuo sam Hotspot VM, najčistiju po krvi Java virtuelnu mašinu.

HotSpot VM jeste virtuelna mašina na bazi steka; kada Java program radi, HotSpot VM učitava kompajlirani fajl bajtkoda (odnosno .class fajl), čiji interpretator ili JIT kompajler čita instrukcije bajtkoda iz fajla i interpretira ih (ili kompajlira) u mašinski kod.
Podaci tokom poziva i izvršavanja metoda (poput lokalnih promenljivih i međurezultata) čuvaju se na steku (steku operanada, o kome će biti reči), a instrukcije bajtkoda operišu nad tim podacima i zatim izvršavaju logiku programa.
Sledeću sliku već smo koristili pri obradi kako JVM izvršava Java kod.

Kada se izvršava metod main, JVM kreira okvir steka (Stack Frame); čuvanjem tabele lokalnih promenljivih, steka operanada, dinamičkog povezivanja, izlaza iz metoda i sličnih informacija podržava i dovršava izvršavanje metoda — okvir steka jeste podjedinica virtuelnog mašinskog steka.

Okvir steka i sam je struktura steka, koja podržava virtuelnu mašinu u pozivu i izvršavanju metoda i prati princip LIFO; svaki okvir steka sadrži informacije o radu metoda, a proces od poziva metoda do završetka izvršavanja odgovara guranju i skidanju jednog okvira steka sa virtuelnog mašinskog steka.

Virtuelni mašinski stek jeste privatno svojina niti; svaka nit ima sopstveni Java virtuelni mašinski stek. Pri pozivu metoda uvek se kreira novi okvir steka, koji se gura na virtuelni mašinski stek i postaje trenutno aktivni okvir steka.
- Guraj: pri pozivu metoda virtuelni mašinski stek tom metodu dodeljuje okvir steka; taj okvir se gura na virtuelni mašinski stek i postaje trenutni aktivni okvir steka. PC registar ukazuje na instrukciju trenutnog okvira steka; sekvenca instrukcija za izvršavanje metoda kreće od te adrese.
- Skini: po završetku izvršavanja metoda, pripadajući okvir steka se uklanja, kontrola se vraća prethodnom okviru steka, a povratna vrednost iz prethodnog okvira steka postaje operand trenutnog aktivnog okvira steka u kom se nastavlja izvršavanje.
Stek operanada (Operand Stack) unutar njega takođe je struktura steka, koja služi za čuvanje međurezultata, argumenata i povratnih vrednosti pri izvršavanju metoda. Kada metod tek počne da se izvršava, njegov stek operanada je prazan.
Tokom izvršavanja metoda stek operanada koristi se za izvršavanje raznih instrukcija bajtkoda. Na primer, instrukcija koja sabira dva broja skinuće dva broja sa steka operanada, sabrati ih i rezultat gurnuti na stek operanada.
Pored toga, sadržaj steka operanada jeste privremen; njegov životni vek podudaran je sa životnim vekom metoda; po završetku metoda i stek operanada biva uništen.

R-da je u temi na Zhihu-u jednom pomenuo:
Prilikom dizajniranja VM-a velika pažnja posvećena je troškovima prenosa i čuvanja koda, jer se pretpostavljalo da će primena biti u ugrađenim sistemima poput ručnih uređaja, set-top boksova i sl., pa je kod trebalo da bude što manji; uz to, implementacija na bazi steka jednostavnija je (bilo na strani kompajlera izvornog koda, bilo na strani virtuelne mašine), a glavni dizajner Džejms Gosling lično je dobro poznavao takav pristup (na primer, ranije je implementirao PostScript virtuelnu mašinu, takođe na bazi skupa instrukcija steka), pa je izabrana varijanta na bazi steka.
Pogledajmo ukratko proces izvršavanja metoda u virtuelnoj mašini na bazi steka, na primeru sledećeg koda:
int a = 33;
int b = 44;
int c = a + b;Komandom javap -c Main možeš pregledati odgovarajući bajtkod, kao što sledi:
Compiled from "Main.java"
public class com.github.paicoding.forum.test.javabetter.jvm.Main {
public static void main(java.lang.String[]);
Code:
0: bipush 33
2: istore_1
3: bipush 44
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: return
}Slikom ćemo objasniti proces izvršavanja instrukcija, grubo ovako.

iload_0gura 33 na stek operanadaiload_1gura 44 na stek operanadaiaddskida 33 i 44 sa steka operanada, sabira ih i rezultat 77 gura na stek operanadaistore_2skida 77 sa vrha steka i smešta ga na poziciju indeksa 2 u tabeli lokalnih promenljivih
Detaljno objašnjenje pojedinačnih instrukcija bajtkoda ostavićemo za sledeće poglavlje; ovde je cilj pre da osetiš razliku između virtuelne mašine na bazi steka i one na bazi registara.
Virtuelna mašina na bazi registara
Pored implementacije na bazi steka, naravno, postoji i ona na bazi registara — na primer LuaVM, koja izvršava Lua jezik, laki skriptni jezik (više na linku).
Lua pre verzije 5.0 zapravo je koristila skup instrukcija na bazi steka, a od verzije 5.0 prešla je na bazu registara, iz dva razloga: prvo, da smanji broj pomeranja podataka i troškove kopiranja pri migraciji; drugo, da smanji broj virtuelnih instrukcija i poboljša efikasnost izvršavanja instrukcija.

Dakle, na primeru Lua-e pogledajmo proces izvršavanja metoda u virtuelnoj mašini na bazi registara.
Prvi korak: instaliraj Lua; ovde koristim macOS, pa instaliram preko brew.
brew install luaKorisnici Windowsa mogu pogledati ovaj dokument: http://lua-users.org/wiki/BuildingLuaInWindowsForNewbies
Instalacija je moguća i preko Lua for Windows:
Napišimo jednostavan Lua kod i sačuvajmo ga kao example.lua.
local a = 33
local b = 44
local c = a + bZatim pogledajmo instrukcije bajtkoda.
luac -l example.luaRezultat je:

main <example.lua:0,0> (6 instructions at 0x600002144080)
0+ params, 3 slots, 1 upvalue, 3 locals, 0 constants, 0 functionsOvo je opis funkcije i pokazuje da je u pitanju glavna funkcija u fajlu example.lua. Sadrži 6 instrukcija. Funkcija ne prima parametre (0+ params), ima 3 slota lokalnih promenljivih (3 slots), 1 promenljivu zatvaranja (1 upvalue), 3 lokalne promenljive (3 locals), bez konstanti (0 constants) i bez unutrašnjih funkcija (0 functions).
Slede konkretne instrukcije:
VARARGPREP 0: priprema varijabilnih argumenata, služi za obradu pristiglih argumenata.LOADI 0 33: učitava ceo broj 33 u registar 0.LOADI 1 44: učitava ceo broj 44 u registar 1.ADD 2 0 1: sabira vrednosti u registru 0 i registru 1 i rezultat smešta u registar 2; odgovara operaciji sabiranja dva broja iz skripte.MMBIN 0 1 6; add: ovo je poziv meta-metode (metamethod) za obradu sabiranja; Time se Lua virtuelna mašina upućuje da pronađe i izvršiaddmeta-metodu. Meta-metode su u Lua-i posebni metodi za preklapanje standardnih operatora.RETURN 3 1 1; 0 out: operacija povratka; vrednost u registru 3 koristi se kao povratna vrednost.1 1znači da se iz registra 3 vraća jedna vrednost, a0 outda nema dodatnih povratnih vrednosti.
Kratak pregled
Prednost pristupa na bazi steka jeste bolja prenosivost, kraće instrukcije i jednostavnija implementacija, ali se ne može nasumično pristupati elementima na steku, pa za istu funkciju treba više instrukcija nego na bazi registara, uz učestalo guranje i skidanje sa steka.
Prednost pristupa na bazi registara jeste brzina, što pogoduje optimizaciji brzine rada programa, ali operandi se moraju eksplicitno navoditi, a instrukcije su i nešto duže.
