Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

DeKugelschieber

Community-Fossil

  • »DeKugelschieber« ist der Autor dieses Themas

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

21

07.06.2014, 18:01

Cool danke für das Tutorial :)

Und @Evrey ich habs oben mal bearbeitet, war etwas unklar von mir. :D

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

22

07.06.2014, 18:10

Ein Assembler ist ein Assembly-Compiler.

LLVM ist großartig, definitiv. Allerdings glaube ich - ich mag mich da irren - dass LLVM für den Anfang ein absoluter Overkill ist. Was du erstmal brauchst ist ja bloß ein Progrämmelchen, das ohne großartige Optimierung den Syntaxbaum in Bytecode übersetzt. Das würde ich dann doch eher zur Übung selbst umsetzen. Allerdings schadet ein kurzes Reinschnüffeln und späteres Nachrüsten nie.

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

23

07.06.2014, 20:46

Dem Tutorial nach zu urteilen ist er nicht sonderlich schwer zu verwenden.
Die Ausgabe davon mal schnell selber umzusetzen ist, glaube ich, nicht so schnell getan bzw. auch sinnlos.

DeKugelschieber

Community-Fossil

  • »DeKugelschieber« ist der Autor dieses Themas

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

24

07.06.2014, 23:34

@Spiele Programmierer: ja aber es geht um den Spaß dabei. Zumindest finde ich es gerade sehr cool da mal etwas ein zu tauchen und rum zu spielen :)

Übrigens geht jetzt auch das:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
_a = 5
print _a
print 6
_b = 7
print _b
_a = 1
print _a

# Ausgabe:
5
6
7
1


Um das noch mal deutlich zu machen, wie meine VM bis jetzt funktioniert:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
    try{
        while(instruction < size){
            cmd = static_cast<unsigned short>(code[instruction]);

            switch(cmd){
                case INSTRUCTIONS::ASSIGN:
                        std::cout<<"assign"<<std::endl;

                    data[varIndex] = stack.top();
                    stack.pop();

                    break;
                case INSTRUCTIONS::VAR:
                        std::cout<<"setID  ";

                    instruction += 2;

                    varIndex = static_cast<int>(code[instruction]);

                    if(data.size() < varIndex+1){
                        data.resize(varIndex+1);
                    }

                        std::cout<<varIndex<<std::endl;

                    instruction += 2;

                    break;
                case INSTRUCTIONS::VAL:
                        std::cout<<"val    ";

                    instruction += 2;

                    stack.push(static_cast<int>(code[instruction]));

                        std::cout<<stack.top()<<std::endl;

                    instruction += 2;

                    break;
                case INSTRUCTIONS::PUSH:
                        std::cout<<"push"<<std::endl;

                    stack.push(data[varIndex]);

                    break;
                case INSTRUCTIONS::PRINT:
                        std::cout<<"output:        ";

                    std::cout<<static_cast<int>(stack.top())<<std::endl;
                    stack.pop();

                    break;
                default:
                    std::cerr<<"Unknown VM command! Program will exit now! "<<std::endl;
                    instruction = size;
            }

            instruction += 2;
        }
    }
    catch(std::exception &e){
        std::cerr<<"VM runtime error: "<<e.what()<<std::endl;
    }


Mit vielen vielen Testausgaben die dann als Assembler in der Konsole stehen.

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

25

08.06.2014, 12:41

Okay, ich bin jetzt ein bisschen wegen des Bytecodes verwirrt. Wenn ich das richtig sehe und deute, dann ist code vom Typ uint8_t[] und ein Opcode ein uint16_t? Zumindest erkläre ich mir so das instruction += 2. Wäre es nicht geschickter, Bytecode in konstanter Länge zu entwerfen? Das geht jetzt in Richtung RISC vs. CISC, aber es hat schon seine Gründe, dass seeehr viele VMs RISC-Maschinen sind und heutige Intel-Prozessoren einen RISC-Kern haben, der über Micro-Code den lächerlich riesigen Befehlssatz emuliert.

Oder anders gesagt:
Wie wäre es, den Befehlssatz auf eine einheitliche Größe zu bringen, z.B. 4 Bytes pro Instruktion? Du könntest z.B. die untersten 5 Bits für den Opcode verwenden und die restlichen Bits, um sofort Integer zu laden. Wie genau du die Bits aufteilst ist dir überlassen und ist überwiegend Geschmackssache. CLua verwendet 6:8:9:9 Bits, MRuby immitiert CLua in vielen Stellen, und meine VM nutzt 8:8:8:8 Bits, um möglicher Weise durch die Register AL und AH zu profitieren. Aber dadurch dass es immer 4 Bytes sind, brauchen diese Formate immer nur ein ++instruction um zum nächsten zu kommen, und meine VM höchstens ein zweites ++instruction, wenn mehr als 16 Bits an Immediates geladen werden müssen. Man versteht den Code schneller und riskiert nicht so schnell, mitten in eine Instruktion hinein zu hüpfen.

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »Evrey« (08.06.2014, 12:55) aus folgendem Grund: Screw you, Orthographie!


DeKugelschieber

Community-Fossil

  • »DeKugelschieber« ist der Autor dieses Themas

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

26

08.06.2014, 13:22

Ich war mir nicht sicher ob ich "nur" 256 Befehle brauche, daher 2 Byte pro Befehl.
Die 4 Byte sind integer, also der einzige Datentyp den ich momentan unterstütze. Daher auch das += 2.
Später möchte ich natürlich Strings und Co haben, daher kann ich sowieso nicht alles einheitlich machen, aber ich überlege auf 1 Byte pro Befehl zu wechseln, weil das wahrscheinlich reichen wird.

Aktuell habe ich eher noch ein Problem mit little endian vs big endian, irgendwie funktioniert meine Umwandlung da nicht richtig:

C-/C++-Quelltext

1
2
3
int Expression::swap4Bytes(unsigned char* c){
    return (c[3]<<24)|(c[2]<<16)|(c[1]<<8)|(c[0]);
}

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

27

08.06.2014, 13:34

Wenn du einen "unsigned char" um 24 Bit nach links shiftest ist das Ergebnis auch undefiniert. ;)
Außerdem gibt es fertige Intrinsics dafür die auch nicht 4 Speicherzugriffe brauchen: http://stackoverflow.com/questions/10525…ian-values-in-c
Ich würde die Instruktion einfach mit einem 16 Bit Integer abspeichern. Befehle fester Breite haben wirklich Vorteile in einer effizienten Implementierung. Verzweigungen in der CPU sind heutzutage häufig so ziemlich das Langsamste an "normalen" Befehlen. Anstatt das letzte Byte durch einen komplexen Decoder herausquetschen, würden im Zweifelsfall richtige Kompressionsalgorithmen viel mehr bringen und die Implementierung durch Einfachheit vereinfachen und beschleunigen.

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »Spiele Programmierer« (08.06.2014, 15:20)


Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

28

08.06.2014, 14:26

Nicht definiert, aber aufgrund der Hardware fast immer 0.
Weiterhin wirst du wahrscheinlich nie im Leben auch nur Hundert sinnvolle Opcodes haben. CLua hat genau 40 Opcodes, MRuby 82 - wenn ich mich nicht verzählt habe. Java, das mehr in Richtung CISC driftet, hat auch unter 256 Opcodes. Ich weiß noch nicht, wie viele Opcodes es bei mir sein werden, aber sicherlich nicht nennenswert mehr als in MRuby.

Wo genau brauchst du eigentlich den Byteswap? Die bisher einzige Endianess-abhängige Stelle meiner VM ist das Layout der Objekt-Handles.
(Und mein bisher einziger Einsatz-Ort eines Hex-Floats...)


P.S.: Ich möchte dich nochmals darauf hinweisen, dass ein "Assembler" ein "Assembly-Compiler" ist. Die Sprache selbst heißt "Assembly", oder auch "Assembler-Sprache".

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Evrey« (08.06.2014, 14:32)


DeKugelschieber

Community-Fossil

  • »DeKugelschieber« ist der Autor dieses Themas

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

29

09.06.2014, 13:38

Also ich habe jetzt erstmal auf ein Byte pro Befehl gewechselt. Das ist auch leicht austauschbar, darum mache ich mir erstmal keinen Kopf darum, nur so gibt es keine endianess Abhängigkeit.
Der Code liegt ja sowieso in einem normalem char array, daher gehe ich mal davon aus dass der CPU Cache ordentlich genutzt werden kann, was z.B. das padding uninteressant macht (und wer weiß was der Compiler daraus macht), oder zumindest erstmal nicht beachtenswert.

Die Abhängigkeit der endianess besteht wenn ich ints und floats in die Binärdatei schreibe. Da sind die Bytes momentan in der falschen Reihenfolge.
Irgendwas mache ich falsch:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
void Value::evaluate(std::ofstream &output){
    evalChildren(output);

    unsigned char cmd = BurningCode::INSTRUCTIONS::VAL;
    output.write(reinterpret_cast<char*>(&cmd), sizeof(unsigned char));

    value = __builtin_bswap64(value); // <-

    output.write(reinterpret_cast<char*>(&value), sizeof(int));
}


Gibt mir nur 0 zurück...

30

09.06.2014, 17:46

Wo initialisierst du eigentlich value? Ansonsten kann ich mir nicht erklären, warum du 0 zurückbekommst. Probier's doch testweise einmal hiermit: http://dl.garishland.de/d4/BinUtil.hpp nur um zu sehen, ob es an der Funktion liegt.

#EDIT: Ich nehme an, dass value ein Member ist?

Werbeanzeige