Problem mit der Parameterübergabe für Inline-ASM in Interpreter-Sprache

08/18/2015 21:51 Shadow992#1
Ich bin momentan dabei Inline-Assembler in meinen Interpreter zu integrieren.

Wichtig dabei ist, dass ich den ASM-Code möglichst nur einmalig und möglichst vor dem "Kompilieren" in Opcodes übersetzen möchte.
Bei ASM-JIT-Kompilierung gäbe es mein beschriebenes Problem gar nicht. Aber JIT soll wirklich der letzte Ausweg sein. Ein Notfallplan sozusagen.

Meine Ursprungsidee war einfach und sollte prinzipiell auch klappen.
Sobald mein Interpreter die Stelle des Opcodes erreicht schreibt er ihn in einen Bereich der ausführbar ist und springt ihn an, anschließend wird zurückgesprungen.

Ein Problem gibt es jetzt nur wenn ich mit dem Code-Stück kommunizieren möchte.
Meine Anfangsidee war "einfach" die Werte auf den Stack zu pushen, die ich verwenden möchte und im ASM-Code muss ich sie dann nur poppen.

Pseudocode mäßig also so:

PHP Code:
ByteArray var = #CompileASM("Pop edx \n Pop ecx \n add edx,ecx")

int var2=10
int var3
=20

Push
(var2)
Push(var3)

Execute(var) 
Das Problem dabei ist, dass mein Interpreter ja erst Push interpretieren muss, weswegen ich nicht weiß ob der Stack bis zum Execute nicht schon wieder zugemüllt ist.
Eine Idee, die ich dann hatte war einen extra Speicherbereich zu allokieren für meinen eigenen Stack.
Also in etwa so:


PHP Code:
ByteArray var = #CompileASM("Pop edx \n Pop ecx \n add edx,ecx")

int var2=10
int var3
=20

int adresse 
VirtualAlloc(...)
int alteAdresse
asm
{
    
mov alteAdresseesp
    mov esp
adresse 
}
Push(var2)
Push(var3)
asm
{    
    
mov espalteAdresse
}

asm
{
    
mov espadresse 
}
Execute(var) 
Aber hier liegt das Problem jetzt, dass ich nicht weiß ob mein Interpreter beim Interpretieren vom "Push" Werte von meinem alten Stack braucht.

Da ich vollen Zugriff auf den zu generierenden ASM-Code habe kann ich auch automatisiert beliebig ASM-Code einfügen lassen falls nötig.

Momentan benutze ich zum Compilen während dem "Compilen von meinem Skriptcode" FASM, um den ASM-Code in den entsprechenden Opcode umzuwandeln.
Wie gesagt würde ich das Ganze JIT machen, könnte ich einfach den Pointer zu den Variablen reinschreiben lassen in den ASM-Code und dann compilen, aber JIT will ich halt eigentlich nicht.

Hat jemand noch eine andere Idee wie ich trotzdem eine Art Parameterübergabe machen kann?
08/19/2015 07:15 Dr. Coxxy#2
mach aus der execute funktion eine mit variablen parametern - da pushst du einfach direkt inline die parameter bevor du var aufrufst - dann kannste sicher sein, dass der stack net zwischendurch kaputtgemacht wurde.

alternativ kannst du der execute funktion einen optionalen stackparameter angeben, also quasi:
Code:
MyStack = new Stack_t();
MyStack.push(MyArg3);
MyStack.push(MyArg2);
MyStack.push(MyArg1);
exectue(var, MyStack);
und in execute dann:
Code:
push esp;
mov esp, MyStack.getPtr();
call var;
pop esp;
08/19/2015 13:17 Shadow992#3
Quote:
Originally Posted by Dr. Coxxy View Post
mach aus der execute funktion eine mit variablen parametern - da pushst du einfach direkt inline die parameter bevor du var aufrufst - dann kannste sicher sein, dass der stack net zwischendurch kaputtgemacht wurde.

alternativ kannst du der execute funktion einen optionalen stackparameter angeben, also quasi:
Code:
MyStack = new Stack_t();
MyStack.push(MyArg3);
MyStack.push(MyArg2);
MyStack.push(MyArg1);
exectue(var, MyStack);
und in execute dann:
Code:
push esp;
mov esp, MyStack.getPtr();
call var;
pop esp;
Oh das ist eine super Idee, vor allem Variante 2 gefällt mir sehr gut, weil ich damit gleichzeitig gewissermaßen garantieren kann, dass der ASM-Code nicht meinen Interpreter-Stack kaputt machen kann.

Danke dir ich glaube so werde ich es machen. :)
08/19/2015 18:11 Dr. Coxxy#4
Quote:
Originally Posted by Shadow992 View Post
Oh das ist eine super Idee, vor allem Variante 2 gefällt mir sehr gut, weil ich damit gleichzeitig gewissermaßen garantieren kann, dass der ASM-Code nicht meinen Interpreter-Stack kaputt machen kann.

Danke dir ich glaube so werde ich es machen. :)
biddö, pass auf, dass dein manueller stack genug platz für rücksprungadresse, lokale variablen der funktion, etc. hat ;)
08/19/2015 20:38 Shadow992#5
Quote:
Originally Posted by Dr. Coxxy View Post
biddö, pass auf, dass dein manueller stack genug platz für rücksprungadresse, lokale variablen der funktion, etc. hat ;)
Ich werde meinen Stack so implementieren, dass der Skripter angeben muss wie groß der Stack sein soll, wobei ich eine Mindestgröße von 8kb festlegen werde (sollte denke ich für die meisten kleinen Code-Stücke reichen).

Noch eine kleine "Styling"-Frage, mein momentaner Ansatz sieht in etwa so aus:

PHP Code:
    Beispielcode:
        
ByteArray var = #CompileASM("Pop edx \n Pop ecx \n add edx,ecx")

        
MyStack = new Stack(8000);
        
MyStack.push(MyArg2);
        
MyStack.push(MyArg1);

        
exectue(var, MyStack);



    
Stack.push-Methode(arg):
        
MyStack.Last_Address+=// Die ersten 4 Bytes werden absichtlich leer gelassen
        
mov [MyStack.Last_Address], arg



    execute
-Methode(opcode,MyStack):
        
mov [MyStack.Start_adress], esp // An der ersten Position von meinem Stack steht immer der original esp-Wert
        
mov espMyStack.Last_Adress // neuen esp Wert setzen
        
call opcode
        mov esp
, [MyStack.Start_adress//Wert zurücksetzen 
Sollte ich vor dem Execute Aufruf die General-Purpose-Register sichern oder sollte ich das dem Programmierer des ASM-Codes überlassen?

Für das automatische Sichern spricht, dass es nicht sehr fehleranfällig ist, weil es eben automatisch passiert.

Auf der anderen Seite, wenn jemand nur eax benutzt, "verschwende" ich einige Zyklen mit dem sichern der anderen Register.

Und da man Inline-ASM wohl nur für sehr zeitkritische Sachen benutzt (oder für Sachen, die anders nur schwer umsetzbar sind, da braucht man dann aber meistens auch wenig Register), wäre ein unnötiges Sichern nicht so optimal.

Und wo ich weiterhin sehr "skeptisch" bin ist beim Sichern der Statusregister. Theoretisch sollte der Compiler meiner Meinung nach niemals so viel Code wegoptimieren, dass er ein cmp/add/etc. vom jeweiligen jne/je/etc. "wegzieht" oder lehne ich mich da zu weit aus dem Fenster? Und sollte daher auch die Statusregister sichern (ist das überhaupt möglich)?
08/19/2015 22:15 Dr. Coxxy#6
Quote:
Originally Posted by Shadow992 View Post
Sollte ich vor dem Execute Aufruf die General-Purpose-Register sichern oder sollte ich das dem Programmierer des ASM-Codes überlassen?

Für das automatische Sichern spricht, dass es nicht sehr fehleranfällig ist, weil es eben automatisch passiert.

Auf der anderen Seite, wenn jemand nur eax benutzt, "verschwende" ich einige Zyklen mit dem sichern der anderen Register.

Und da man Inline-ASM wohl nur für sehr zeitkritische Sachen benutzt (oder für Sachen, die anders nur schwer umsetzbar sind, da braucht man dann aber meistens auch wenig Register), wäre ein unnötiges Sichern nicht so optimal.
fasst es eigtl schon gut zusammen, geschmackssache, im zweifel wird bei ner scriptsprache aber perfomance wahrscheinlich eh nicht sooooo ne riesenrolle spielen.

Quote:
Und wo ich weiterhin sehr "skeptisch" bin ist beim Sichern der Statusregister. Theoretisch sollte der Compiler meiner Meinung nach niemals so viel Code wegoptimieren, dass er ein cmp/add/etc. vom jeweiligen jne/je/etc. "wegzieht" oder lehne ich mich da zu weit aus dem Fenster? Und sollte daher auch die Statusregister sichern (ist das überhaupt möglich)?
O.o wo soll denn der compiler in deinem asm was optimieren?
compiler (zmdst der ms vcc) optimiert kein inline asm.
08/20/2015 02:47 Shadow992#7
Quote:
Originally Posted by Dr. Coxxy View Post
fasst es eigtl schon gut zusammen, geschmackssache, im zweifel wird bei ner scriptsprache aber perfomance wahrscheinlich eh nicht sooooo ne riesenrolle spielen.
Alles klar, denke dann auch dass ich die "alles sichern"-Methode nehmenw erde.
Quote:
Originally Posted by Dr. Coxxy View Post
O.o wo soll denn der compiler in deinem asm was optimieren?
compiler (zmdst der ms vcc) optimiert kein inline asm.
Naja gehen wir von etwas derartigen in Pseudo-C aus:
PHP Code:

InterpretOpcode
(...)
if(
i==0)
{
  
i++;

Dann wird der Compiler es in etwa in das übersetzen:
PHP Code:

push 
...
call InterpretOpcode
cmp eax
0
jne end_if
inc eax
end_if

Wenn der Compiler aber (aus welchem Optimierungsgründen auch immer), das in folgenden Code umwandeln würde:

PHP Code:

cmp eax
0
push 
...
call InterpretOpcode
jne end_if
inc eax
end_if

Müsste ich das Statusregister ebenfalls sichern, weil es ja sein kann, dass der Aufruf das Register verändert.

Was ich jetzt aussagen wollte ist, dass ich bezweifle, dass die zweite (optimierte) Variante jemals so oder so ähnlich in der "freien" Natur vorkommt. :D
Oder liege ich da falsch?
08/20/2015 10:29 Jeoni#8
Das Flag-Register wird in den Konventionen, an denen sich viele Compiler (MSVC eingeschlossen) als "Caller-Saved" angesehen, da sehr viele Instructions Einfluss auf das Flag-Register nehmen. Das heißt, dass angenommen werden muss, dass eine aufgerufene Funktion dieses Register verändert. Braucht man es nach einem call noch, muss man es davor sichern und danch wiederherstellen. Hab noch nie gesehen, dass der Compiler sowas generiert.
Auch einige Register gelten als Caller-Saved. Die muss eine aufgerufene Funktion also nicht sichern. Andere Register gelten als Callee-Saved. Diese müssen am Ende einer Funktion den gleichen Wert wie am Anfang aufweisen.
Welches Register nun was ist, kann [Only registered and activated users can see links. Click Here To Register...] nachgesehen werden.
Mit freundlichen Grüßen
Jeoni
08/20/2015 18:40 Dr. Coxxy#9
falls du wirklich funktionsübergreifend einen gleichen kontext in deiner scriptsprache anbieten willst, sollteste dir mal angucken wie windows die threads und deren kontexte verwaltet, stichwort get/setthreadkontext und der kontext struktur angucken (findeste in der winnt.h) sowie mal bissle guglen oder windows internals lesen - da kannste dir evtl. bissle abgucken.

EDIT:
achsoo, jetzt erst dein problem verstanden - nein, solange du dich an die von jeon erwähnten regeln innerhalb deines assemblers hältst, bzw. durch speichern der entsprechenden register vor ausführen des scriptcodes sicherstellst, dass diese beachtet werden, kriegst du keine probleme.