divendres, 23 d’abril del 2021

JailScript (IV): La Màquina Virtual

Aquest es el quart escrit d’una sèrie que repassarà el desenvolupament d’un llenguatge de programació amb el qual poder fer JailGames de forma ràpida i còmoda. Hui començarem a vore com s’executarà el codi que escrivim amb JailScript.

Els fonaments

Una de les primeres cosses que em vaig plantejar a l’hora de dissenyar el llenguatge no està realment relacionat amb el llenguatge... be, sí... però no... es complicat...

Els llenguatges de programació clàssics es compilen a codi màquina. Un programa es poc mes que una "ristra" de codi màquina, instruccions que el processador entén directament. En el nostre cas, qui volem que interprete les nostres instruccions no es el processador directament, sinó el nostre programa. Així doncs, tenim dos possibilitats:

  • Interpretar el llenguatge al vol: Així es com funcionaven els BASICs de la era de 8 bits, o inclús GW-BASIC i familiars. Conforme es van llegint els comandos de text s’interpreten i s’executen. Açò te el problema, entre altres, de ser extremadament lent.
  • Compilar el llenguatge a un "codi màquina propietari", o byte code, que després una màquina virtual interpretarà i executarà. D’aquesta forma, la part mes lenta, que es anar llegint el text, decidint quines instruccions són i com actuar, es fa una vegada, abans d’executar res, generant una "ristra" d’instruccions. La màquina virtual es comportarà com si fos un "emulador", llegint aquestes instruccions i executant-les. Encara que es prou mes lent que un programa compilat a codi màquina, es molt més ràpid que l’anterior opció. Així funciona Java o .NET. També Lua, que es integrable en el teu propi codi; o Javascript en els navegadors; o Python, PHP, Ruby...

Per què?

Si es més ràpid compilar, per exemple, des de C a codi màquina i au, per què calfar-se el cap? Primera, perquè podem: Un JailGame funciona perfectament sobrat en qualsevol màquina de hui en dia... o de fa 20 anys inclús. Segona, ja que podem, tindre una màquina virtual ens dona certs avantatges i comoditats:

  • No hi ha que recompilar cada vegada. Compilar en gcc es prou mes lent que el que tardarà el nostre parser. A més d’estalviar-se lo farragós que es compilar un binari "legal" en Mac hui en dia.
  • Depuració. Escriure un depurador per a la màquina virtual es relativament fàcil i pots controlar tots els aspectes.
  • Portabilitat. Si es volen dur els jocs a altra plataforma, nomes hi ha que escriure la màquina virtual per a eixa plataforma, i els jocs en principi funcionaran gratis.
  • Comoditat. C/C++ mola molt i es super potent, però treballar amb una plataforma que estiga centrada en fer jocs, llevant tot el que es innecessari o superflu de davant, ajuda a que el desenvolupament siga mes fluid.

Tipus de màquines

Generalment hi ha dos tipus de màquines més usades. Màquines de registres i Màquines de pila.

Les màquines de registres son les més habituals, sobre tot per a processadors reals. El processador te accés a la memòria del sistema, però també disposa de una memòria interna super ràpida, anomenada registres, que usa per a fer les operacions que te en eixe moment entre mans. Així, es habitual que tinguen un registre acumulador A per a les operacions aritmètiques, registres X o Y per a iteradors, etc... els registres son molt ràpids, però molt cars, així que solen haver nomes uns pocs. En el cas de ser màquines virtuals, solen haver més registres i solen ser menys especialitzats, podent fer quasi totes les operacions des de quasi tots els registres. Dels dos tipus es el més difícil de generar codi, pel fet de tindre que gestionar la pressió sobre els registres a l’hora de assignar valors i operacions al limitat nombre de registres. També sol ser, no obstant, el tipus de màquina més ràpid.

Les màquines de pila no son habituals com a processadors reals, i menys hui en dia. En aquest tipus de màquines els operadors i les operacions es van deixant caure en una pila executant-se seguint la notació polaca inversa. Aquest tipus de màquina es més fàcil de fer i més fàcil de compilar per a ella, així que en principi es el tipus que he triat.

BYTECODE

Les instruccions de que disposarà la màquina aniran evolucionant durant el desenvolupament per a adequar-se a les necessitats, però ja podem vislumbrar algunes instruccions que necessitarem:

PUSH <numero>
Per a ficar un número en la pila
POP
Per a traure un número de la pila
LD <adreça>
Per a carregar un número d’una adreça de memòria a la pila
ST <adreça>
Per a guardar el numero al cim de la pila en memòria
ADD, SUB,
MUL, DIV,
AND, OR,
NOT, NEG,
SHR...
Operacions matemàtiques varies
JMP <adreça>
Bot incondicional del codi a l’adreça especificada
JT <adreça>,
JNT <adreça>
Bot si el cim de la pila es zero (o si no es zero)
CALL <adreça?>
Cridar a una funció interna (escrita en el propi llenguatge)
RET
Tornar des d’una crida a una funció interna
CALLEX <valor>
Cridar a una funció externa (escrita en C)

Exemple

Per a il·lustrar un poc com funcionaria, un exemple de codi a pseudo-bytecode:

var a as number = 4

a = a + 5




PUSH 4
ST a

LD a
PUSH 5
ADD
ST a
Fica un 4 en la pila
Agafa’l de la pila i guarda'l en "a"

Agafa el que hi ha en "a" i fica-ho en la pila
Fica un 5 en la pila
Suma els dos números al cim de la pila
Guarda el resultat en "a"

Es pseudo-bytecode perquè en compte de ficar una adreça de memòria corresponent a la variable he ficat la variable, per estalviar-se el concepte de variable-memòria i la gestió de memòria que vorem en un capítol més avant.

La Pila

Per últim, volia il·lustrar com funciona la pila, per si algú no ha captat la idea intuïtivament. Anem a executar el codi anterior i vorem com està la pila en cada moment. Al principi la pila està buida.

	PILA: []

La primera instrucció fica un 4 en la pila:

	PUSH 4  'PILA: [4]

La següent agafa el valor que hi ha al cim de la pila, el lleva, i el guardarà en memòria

	ST a	'PILA: []

Ara agafa el que hi ha en memòria i ho fica en la pila:

	LD a	'PILA: [4]

Ara fica un 5:

	PUSH 5	'PILA: [4, 5]

Ara executa la instrucció ADD, que agafa els dos valors al cim de la pila, els suma i deixa en la pila el resultat:

	ADD	'PILA: [9]

Per últim, agafa el que hi ha al cim de la pila i ho guarda en memòria

	ST a	'PILA: []

 

Mes avant vorem més exemples de bytecode i com funciona la pila. Crec que per hui ja hi ha prou. El pròxim post escriuré sobre el tokenitzador, que es altre tema satèl·lit al llenguatge en si.

Cap comentari:

Publica un comentari a l'entrada