[x86] vcpu - runtime/compiler (Script - Backend)

Anwendungen, Tools, Userlibs und anderes nützliches.
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

[x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von cxAlex »

Servus.
Nach langer Abstinenz melde ich mich wieder zurück von den Toten, diesmal mit einem hybriden Projekt aus PureBasic und C.

Mit vcpu möchte ich eine Plattform entwickeln um eigene Skriptsprachen zu entwickeln und in seine eigenen Programme einzubinden, bzw. standalone laufen zu lassen. Das Prinzip des Ganzen basiert auf 3 Stufen:

- Ein Cross-Compiler übersetzt den High-Level Source in eine ASM - Ähnliche Zwischensprache (ByteCode - ASM)
- Der BC-ASM wird von einem weiteren Compiler in ByteCode gewandelt der direkt von der Runtime ausgeführt werden kann.
Dieser Compiler versucht den Code dabei so gut es geht zu optimieren, mit besonderem Augenmerk auf Code-Fragmente die durch die
automatische Cross-Compilierung im vorigen Schritt entstanden sind.
- Der ByteCode wir anschließend mit der Runtime ausgeführt die entweder standalone läuft oder (als DLL) direkt in eurem Programmen

Die Runtime wird dabei aus Performance-Gründen ausschließlich in C programmiert, PB ist da um FAKTOREN (10x-20x!) zu langsam. Zu den technischen Details, die Runtime ist eine Register-Maschine, d.h. wir benötigen für Berechnungen usw. keinen Stack. Die Zahl der Register passt sich der Zahl der benötigten Variablen an, es ist aber auch möglich ein Minimum an Registern zu reservieren (das ist jetzt einfach mal so auf 0xFF gesetzt).

Die Register sind Variant-typen mit den den Möglichkeiten int/uint/float (unsigned - 32 Bit - Typen, noch ein Grund C für die Runtime zu wählen). Es gibt also keinen gesonderten FPU - Arithmetik Befehle, die normalen Arithmetik und Logik - Befehle können genauso auf Float - Inhalte angewandt werden, auch gibt es native type-cast Befehle. Auch gibt es einige Befehle mit verschiedener Parameter Anzahl (besonders die Arithmetik Befehle), z.B. ADD [a] (uint a += uint b) / ADD f[a] [c] (float a = float b + float c). Natürlich sind das dann doch ziemlich verschiedene Befehle im ByteCode - Strom, aber darum kümmert sich der BC - Compiler vollautomatisch.

Die Runtime ist voll Thread - basiert (pthread) und der Ausführungsfluss ist steuerbar (start/stop/pause/resume/reset/...) und es kann eine beliebige Anzahl von Instanzen erstellt werden. Zur Erweiterung des Befehlssatzes wie auch zur Kommunikation der Runtime mit eurem Programm gibt es eine IO - Schnittstelle mit einer beliebig definierbaren Anzahl von Ports.

Desweiteren gibt es Unterstützung für:
  • Anonyme Labels
  • Calls
  • Native Arrays


Native Listen, Memory - Zugriffe, ein Interrupt-Controller und darauf basierte Timer sind in Arbeit.

Die Runtime und der BC-ASM zu ByteCode Compiler sind schon voll lauffähig, die Optimierungsroutinen des Compilers sind noch in Arbeit (Peephole - Optimierungen nach dem Pattern-Matching Prinzip, DeadCode Detection, ...), der Cross-Compiler ist noch in Arbeit.

Daher habe ich zum Testen eine kleine IDE erstell die ich aus meinem µCEmulator Projekt portiert adaptiert habe:
(Nur um das nochmal klarzustellen, händisch auf dieser Ebene Code zu schreiben ist in der Endversion nicht vorgesehen,
natürlich ist es möglich aber dann nur eventuell zum fine-tuning, also bitte keine zu hohen ansprüche an die Syntax ...)


Bild

Vermutlich wird es nicht allzu viele hier geben die das benötigen können, und wenn doch hab ich hier immer noch nicht genug gesagt.

Aber eine vollständige Befehls/Syntax/OpCode/Dokumentation usw. wird nachgereicht sobald vollständig vorhande, Ich wollte hier nur mal einen
1. lauffähigen Vorgeschmack präsentieren und wer will kann sich ja mal mit den mitgelieferten Examples spielen.

Download

Der Source wird auf Google Code gehostet.
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Benutzeravatar
Thorium
Beiträge: 1722
Registriert: 12.06.2005 11:15
Wohnort: Germany
Kontaktdaten:

Re: [x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von Thorium »

Wenns dir um Performance geht, warum dann nicht gleich nach Maschinencode übersetzen?
Zu mir kommen behinderte Delphine um mit mir zu schwimmen.

Wir fordern mehr Aufmerksamkeit für umfallende Reissäcke! Bild
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8679
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: [x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von NicTheQuick »

Thorium hat geschrieben:Wenns dir um Performance geht, warum dann nicht gleich nach Maschinencode übersetzen?
Du meinst eine JIT-Compiler?
Bild
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

Re: [x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von cxAlex »

Thorium hat geschrieben:Wenns dir um Performance geht, warum dann nicht gleich nach Maschinencode übersetzen?
Wegen der IO - Schnittstellen zu deinem Programm, usw. Das ganze ist auch in die Richtung Programmierbare Makro/Plugin - Schnittstelle gedacht. An JIT hab ich natürlich auch schon gedacht, wobei das dann doch wieder eine ganz andere Liga ist, da stecken dann schon komplexere Algorithmen dahinter nur um überhaupt zu entscheiden welche Code-Teile den jetzt nativ kompiliert werden ohne das die für de Entscheidungsprozess und den Kompilationsprozess benötigte Zeit den Vorteil wieder eliminiert, ... . Vor allem kann man sowas erst später einbauen wenn der Interpreter - Kern steht und stabil läuft.

Und mit "wenns dir um Performance geht" meinst du wohl auch das der Kern in C statt in PB geschrieben war. Nun, es gab den Kern in PB, der C - Source war am Anfang nur ein PB - Port. Der PB - Code war doch schon ziemlich gut optimiert, und die unoptimierte erste C - Version war bereits um ein x - Faches schneller, wie gesagt im Schnitt Faktor 10-20 (Ich hab zum üben so ziemlich alle meine PB - Includes nach C portiert, doch einiges an nützlichen Funktionen, und da war teils eine Steigerung bis zum Faktor 200 drin!). Es geht mir hier nur darum, es ist in beiden Fällen etwa diesselbe Tipparbeit, und wenn die eine Sprache (natürlich auch wegen eines viel heftiger optimierenden Compilers) 10-20x mal schneller ist ohne Nachteile, ist klar was ich wähle.

Zum eigentlichen Projekt, es geht gut vorran, derzeit ist ein Konverter Infix->Reverse Polish Notation->ByteCode ASM in Arbeit (und auch schon ziemlich fertig) der logische und Arithmetische Ausdrücke direkt im ASM - Source ermöglicht und so die Arbeit des Cross - Compilers nochmal erleichtert.

Gruß, Alex
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Benutzeravatar
Thorium
Beiträge: 1722
Registriert: 12.06.2005 11:15
Wohnort: Germany
Kontaktdaten:

Re: [x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von Thorium »

Ja genau, ich meine JIT.
Naja du schreibst ja das der Bytecode optimiert wird. Deshalb wäre es vieleicht interessant sich das Bytecodeoptimieren zu sparen und lieber nach Maschinencode zu übersetzen und den zu optimieren. So kompliziert ist das garnicht. Das komplizierteste ist der Encoder, der dir die Instruktionen baut, x86 hat leider ein komplexes Format für die Instruktionen.
Den Overhead der Kompilierung kannst du praktisch ignorieren, der ist sehr gering.
Zu mir kommen behinderte Delphine um mit mir zu schwimmen.

Wir fordern mehr Aufmerksamkeit für umfallende Reissäcke! Bild
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

Re: [x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von cxAlex »

Nein, da ist Eben bei JIT der Unterschied: Der ByteCode wird VOR der Ausführung optimiert (Source -> ByteCode). Das würde so und so geschehen und die JIT würde den Bytecode jetzt WÄHREN der Ausführung in Maschienencode übersetzen. Also spielt die Übersetzungszeit ByteCode -> Maschienencode sehr wohl eine Rolle, denn wenn dieser Prozess länger dauert als der Zeitgewinn durch die beschleunigte Ausführung ist das kontraproduktiv. Weshalb der JIT Compiler eben sehr genau entscheiden muss was er kompiliert und wie stark er optimiert! Vor allem muss der Maschinencode Schnittstellen zum Bytecode enthalten um z.B. Variablen usw. global integer zu halten, da ja nur teilweise in Maschinencode übersetzt wird und wir nun einen hybriden Code haben.

Gruß, Alex
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Benutzeravatar
Danilo
-= Anfänger =-
Beiträge: 2284
Registriert: 29.08.2004 03:07

Re: [x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von Danilo »

cxAlex hat geschrieben:Nein, da ist Eben bei JIT der Unterschied: Der ByteCode wird VOR der Ausführung optimiert (Source -> ByteCode). Das würde so und so geschehen und die JIT würde den Bytecode jetzt WÄHREN der Ausführung in Maschienencode übersetzen. Also spielt die Übersetzungszeit ByteCode -> Maschienencode sehr wohl eine Rolle, denn wenn dieser Prozess länger dauert als der Zeitgewinn durch die beschleunigte Ausführung ist das kontraproduktiv. Weshalb der JIT Compiler eben sehr genau entscheiden muss was er kompiliert und wie stark er optimiert! Vor allem muss der Maschinencode Schnittstellen zum Bytecode enthalten um z.B. Variablen usw. global integer zu halten, da ja nur teilweise in Maschinencode übersetzt wird und wir nun einen hybriden Code haben.
Natürlich wird schon bei der Übersetzung des SourceCodes in ByteCode optimiert, was man optimieren kann.
Konstante Ausdrücke zusammenführen; sich wiederholende Berechnungen aus Schleifen rausholen, wenn immer
gleiches Ergebnis heraus kommt, und das ganze andere Standardzeug.
Der ByteCode sollte sicherlich platformunabhängig sein, also muss man beim entwickeln des BytesCodes zum
Beispiel die x86/x64 Architektur vergessen und nicht schon darauf hinarbeiten.
Beim kompilieren zur Laufzeit kann dann aber nochmal etwas optimiert werden. Wenn der ByteCode auf einem Prozessor
mit MMX gejitted wird, kommt eben anderer Code raus als auf einer Maschine mit SSE3 oder auf einem ARM.
Das ist ja der Vorteil von JIT Kompilierung, das direkt auf die Zielmaschine optimiert wird. Bei statischer 486-Codeerzeugung
läuft es dann auf einem SSE3-Prozessor halt nicht schnellstmöglich. Mit JIT kann man das aber machen.

Am sinnvollsten ist wohl einfach mehrere grosse Tabellen anzulegen, worin Du ByteCode-Folgen und Target-
Maschinencode speicherst. Eine Tabelle für 486, MMX, SSE, SSE2, SSE3. Am Ende ist der JIT-Codegen simpel gesagt
eine Pattern-Matching-Funktion, die die Tabellen nach entsprechenden ByteCodeFolgen durchsucht, und einen
Treffer/Match findet. Dabei sollte die unterste Tabelle (zum Beispiel für 486er Code) alles enthalten was mit dem
ByteCode möglich ist. Die darüber liegenden Tabellen müssen nur das enthalten, was sie speziell optimieren können.

Beim JIT-Kompilieren stellst Du nun erstmal fest was der Rechner kann, auf dem gerade ausgeführt wird. Wenn er
beispielsweise SSE2 kann, dann ist das Deine obere Start-Tabelle. Deine Pattern-Matching-Funktion nimmt nun einen
Stück ByteCode und sucht in Tabelle SSE2 nach einem Treffer. Wird dort nichts gefunden, sucht es in der darunter
liegenden Tabelle usw. - bis hinunter zur untersten Tabelle, die auf jeden Fall einen Treffer für alles Mögliche enthalten muss.

Solche Tabellen zu erstellen ist recht aufwändig, aber die Performance zur Laufzeit sollte dann wirklich sehr gut sein.

Dabei muss der JIT-Compiler nicht unbedingt spezielle Teile auswählen, die er immer JIT kompiliert und dann ausführt (was das
System noch viel komplizierter macht). Das macht nur bei riesigen ByteCode-Programmen Sinn.
Kleinere Projekte lassen sich damit in Millisekunden oder ein paar 1/100 Sekunden komplett übersetzen.
20.000 Zeilen PB-Code sind auch recht schnell kompiliert. Bei binärem ByteCode geht das dann nochmal um ein
Vielfaches schneller, da das ganze parsen von SourceCode und die Standardoptimierungen entfallen.
Von daher denke ich, dass Du ruhig alles komplett kompilieren kannst. Mit der richtigen Methode geht das
wirklich schnell.

Je nachdem, ob es zu Deinem System passt, muss man auch nicht jedes mal die Scripte neu kompilieren. Bei einer
Script-Engine für ein Spiel oder für eine komplexe, scriptbare Anwendung, kann man das Kompilat auch abspeichern.
Bei Aufruf zum ausführen des Scriptes "beispiel.script" prüfst Du einfach ob im gleichen Verzeichniss ein "beispiel.kompilat"
mit gleichem Datum vorhanden ist. Wenn Ja, dann nimmst Du das fertige Kompilat und führst es aus.
Wenn Nein (Kompilat nicht vorhanden oder Datum der Dateien ist unterschiedlich (was auf eine Script-Änderung hinweist)),
wird für diese Maschine kompiliert und das Kompilat gespeichert. Das Datum des resultierenden Kompilats wird auf das
gleiche Datum wie das Script gesetzt.
Das wäre dann die Finale Stufe, wo nur beim ersten Mal und nach Änderungen am Script auf dem Target kompiliert werden muss.
Beim zweiten Start ohne Änderungen kann sofort ausgeführt werden (evtl. Checksumme o.ä. verwenden um Manipulationen
am Kompilat zu vermeiden).

Das sind so die üblichen Techniken die überall verwendet werden. Der letzte Abschnitt zum Beispiel auch bei .NET, wo
man mit ngen auf dem Zielrechner ein Kompilat in einem Assembly-Cache erzeugen kann, so dass Deine Anwendung
schneller startet.
SlickEdit (ein super Editor für Programmierer für viele Platformen und Programmiersprachen) hat auch so ein Scripting-System.
Die Source-Scripts haben die Endung .e, die vorkompilierten ByteCode-Dateien die Endung .ex. Wird eine Änderung am Source.e
entdeckt, wird Source.ex neu erstellt, andernfalls wird direkt Source.ex geladen und ausgeführt.
cya,
...Danilo
"Ein Genie besteht zu 10% aus Inspiration und zu 90% aus Transpiration" - Max Planck
Benutzeravatar
Thorium
Beiträge: 1722
Registriert: 12.06.2005 11:15
Wohnort: Germany
Kontaktdaten:

Re: [x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von Thorium »

cxAlex hat geschrieben:Nein, da ist Eben bei JIT der Unterschied: Der ByteCode wird VOR der Ausführung optimiert (Source -> ByteCode). Das würde so und so geschehen und die JIT würde den Bytecode jetzt WÄHREN der Ausführung in Maschienencode übersetzen. Also spielt die Übersetzungszeit ByteCode -> Maschienencode sehr wohl eine Rolle, denn wenn dieser Prozess länger dauert als der Zeitgewinn durch die beschleunigte Ausführung ist das kontraproduktiv.
Du kannst die Kompilierungszeit tatsächlich vernachlässigen.
Ich hatte mal an einem Recompiler x86 zu ARMv7 gearbeitet und wollte dem aus den von dir genannten Gründen statisch machen. Also das komplette Executable wird erst übersetzt und dann ausgeführt. Dabei gibts aber einige Probleme, die sich dynamisch viele einfacher lösen lassen und einige Diskussionen und Tipps mit Entwicklern aus der Emulatorszene haben mich überzeugt das der Overhead von dynamischer Recompilierung vernachlässigbar ist und es ist tatsächlich so. In den meisten fällen gab es keinen messbaren Unterschied zwischen statisch und dynamisch.
Zu mir kommen behinderte Delphine um mit mir zu schwimmen.

Wir fordern mehr Aufmerksamkeit für umfallende Reissäcke! Bild
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

Re: [x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von cxAlex »

Alles schön und gut und interessant, nur wie gesagt muss dafür erst mal der Interpreter/Compiler stabil laufen, bzw. die Sprach - Syntax fertig definiert.

Kleines Zwischen - Update, es ist nun möglich direkt in Infix - Notation im ASM - Source zu schreiben, was die Arbeit für den Cross - Compiler nochmal erleichtern sollte. Der ByteCode - Compiler setzt dafür den vollständigen shunting yard - Algorithmus rum, die Syntax ist exakt dieselbe wie in C (bis auf Pointer - Operationen, Pointer gibts hier nicht). Also, z.B. {[x] = -(++[y]/([c] *= 4)) + [z]--} (die {} kennzeichnen eine Infix - Sektion) wäre eine absolut korrekte Syntax. Des weiteren wurde nun endlich der Optimierer aktiviert, klassisches Peephole - pattern matching mit ein paar anderen globaleren Optimierungen. Die Optimierung ist in der IDE ein/ausschaltbar um den Unterschied zu sehen, bzw. man kann sich die Optimierte Version anzeigen lassen (Assembly). Es sind sicher noch nicht alle möglichen Peephole - Pattern eingebaut, falls jemand im erzeugten Source noch ständig wiederkehrende, optimierbare Muster findet bitte melden, desto mehr Pattern desto besser. Außerdem gibts es jetzt das "proc" Keyword, ein Compiler - Macro das die nötigen Labels, Pop ... für eine Procedure die per "call" aufgerufen wird automatisch erzeugt.

Bild

Download - Link im 1. Post
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

Re: [x86] vcpu - runtime/compiler (Script - Backend)

Beitrag von cxAlex »

kleines Update.

Die Ausführung-Einheit wurde etwas optimiert, und der Compiler kennt nun ein paar mehr Komfort - Makros (If,Else,..) und erzeugt automatisch daraus die benötigten Labels/Compares ... . Der Source ist schon online, Executable - Previews heute Abend oder morgen, je nachdem wie ich dazu komme.

Die Geschwindigkeit kann sich (für eine Interpretierte Sprache) bereits jetzt durchaus sehen lassen, in so ziemlich allen Test (Array lesen, schreiben, Sprünge, Funktionsaufrufe, Rekursive Funktionsaufrufe, Mathematische Algorithmen, Speicherzugriffe, GGT, KGV, MIN, MAX, Floating - Point Berechnungen) liegt die Ausführungsgeschwindigkeit ziemlich exakt bei 1/5 - 1/4 eines nativen PB - Kompilats, und es ist noch genug Spielraum vorhanden um in Bereiche von 1/3 ( 1/2 :mrgreen: ) zu kommen, ohne JIT bisher. Des weiteren Verfügt die CPU nun über einen programmierbaren Interrupt - Controller der beliebig viele Interrupts verwalten kann, bzw. Interrupts lassen sich von außerhalb der CPU triggern.

Gruß, Alex
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Antworten