Virtual Machine Ontwikkeling
Dit document gaat over ervaringen met het maken van een simple virtual machine. Op sommige plekken is het erg technisch op andere plekken niet. Soms wordt uitgelegt waarom ik gekozen heb voor bepaalde onderdelen.
Interpreter/Virtual machine
De interpreter is geschreven in C voor snelheid en portabiliteit (dit betekent niet dat de interpreter snel of portable is, ofzo). De virtual machine wordt op dit moment door mij gebruikt om ideeen uit te testen.
Het is ook zo dat ik op een later moment een compiler ga schrijven, zodat het niet nodig is dat de assembly language handig is voor mensen (alhoewel dit op sommige plekken wel handig is).
De huidige instructies/opcodes zijn: simpele integer berekening, subroutines en branches.
De interpreter heeft op dit moment 256 integer registers, maar
misschien veranderd dit nog wel. Bij het opstarten bevatten alle
registers de waarde 0.
De interpreter heeft nu ook drie stacks. Een voor ints, een voor objects en een voor method/function calls. Hierdoor wordt de VM veiliger, omdat een programma geen return-adressen van de stack kan poppen.
Hoewel al die integer registers wel leuk zijn, is het handiger om maar een soort registers te hebben. Het gaat dan om een register dat alle soorten waarden kan bevatten.
De manier waarop locale variabelen gebruikt kunnen worden is via de registers. Globale variabelen kunnen via een symboltable (wordt waarschijnlijk een soort hash object) gebruikt worden.
De mogelijkheden op mijn todo lijst zijn: objecten, io, uitgebreidere string mogelijkheden en variablen.
Objecten
De objecten die hier genoemd worden, zijn geen echte objecten zoals in
Java, maar meer een combinatie van data en functies. (in C taal:
structs met functie pointers).
Er moeten nog wel instructies komen voor het gebruik van de objecten.
De instructies die nodig zijn:
- Creëeren/Allocatie van nieuwe objecten.
- Laden van objecten uit andere bestanden.
- Aanroepen van functies/methods op die objecten.
- Introspectie: meta-data van het object bekijken.
- Sommige operatoren, bv.: array indexing -> array[i] = r5, of r1 = array[i].
Ik wil ook garbage collection en JIT compilation gebruiken, maar dan zal ik eerst moeten uitzoeken hoe dat werkt.
Verwijderde functionaliteit.
Er zijn een aantal functies, die ik verwijderd heb. Sommige omdat de VM er makkelijker van werd, sommige omdat de VM er traag van werd en sommige werden verwijderen vanwege beide.
- predicates = (boolean registers). Nu worden gewone int registers hiervoor gebruikt.
- voor elke instructie een predicate. Heel traag, want voor elke instructie werd gekeken of deze uitgevoerd moest worden.
- vast gestelde registers. Waren niet meer nodig omdat constanten in de instructies opgenomen kunnen worden.
Assembler
De assembler is geschreven in Perl. Dit heeft als voordeel dat deze makkelijk veranderd kan worden (en dat is al een aantal keer gebeurd).
Op dit moment is er ondersteuning voor integers, strings (heel weinig), labels en constanten.
Alle labels zijn compile-time constants. Hierdoor is het niet echt mogelijk om deze te gebruiken in een functie aanroep buiten het huidige bestand.
Daarvoor zou een tabel moet alle symbolen in het bestand moeten komen. Deze symbolen bestaan dan uit een naam en een offset in de bytecode.
Het zou ook handig zijn om lokale labels te kunnen gebruiken. Zo houden wij meer namen over om voor labels te gebruiken. De lokale labels zullen niet in de symbol table die hierboven besproken is.
Het is nu ook mogelijk om string in de regels op te nemen: set r0, "string", dit scheelt weer een hoop werk. Dit is geïmplementeerd op de volgende manier.
- Zet alle strings om naar een code. Opeenvolgend: __STRi, waar i een getal is.
- Maak een constante declaratie aan. .constant __STRi stringwaarde
- De rest werd al geregeld door de assembler. De assembler zet de labels om naar een nummer en de string komen in de constanten tabel.
Er is nu ook de mogelijkheid voor externe labels. Dit betekent dat een label opgezocht kan worden door de VM. De labelnamen worden in de header van het bestand opgenomen.
Bestandsformaat
Het bestandsformaat is nog een veranderingen onderhevig. Het is vrij simpel.
| beschrijving | size |
| externe labels (of zijn dit globale labels.) | total * (sizeof(int) + sizeof(int) + strlen(label) + 1) |
| constante strings | total * (sizeof(int) + strlen(string) + 1) |
| bytecode | rest |
Elk van deze onderdelen bestaat uit meerdere onderdelen. De externe labels bestaan uit strings en offsets. Het begint met een int die aangeeft hoeveel externe labels er zijn. Daarna volgen er zoveel externe labels. int (string length), char[string length] string data, int (offset). De offsets beginnen bij het begin van de bytecode. De eerste instructie heeft offset 0. Daarna komen de constanten. Dit zijn alleen maar strings. De eerste string is constante 0, een de tweede is constante 1.
Util progs
De util progs zetten Opcode definities om naar een c-bestand en pm-bestand. Deze bestanden worden gebruikt door de assembler en de interpreter.
Todo:
verder uitbreiden.
Let op: veel ideeen heb ik overgenomen uit andere virtual machines, zoals Parrot. Het is niet zozeer het gebruiken van de code, maar meer het hergebruik van de manieren. Bv. registers, ops als functiepointers, automatisch genereren van c-code uit ops declaraties m.b.v. Perl scripts.
Het is vooral een oefening om met Perl en C te werken, een virtual machine maken en gestructureerd programmeren.