Würde ich heutzutage ein auf dem C-64 nativ laufendes Editierwerkzeug bauen, würde ich hart überlegen, das in einer Pascal-artigen Hochsprache (mit punktueller Inline-Assembly) einzutippen.
Hallo Besucher, der Thread wurde 4,3k mal aufgerufen und enthält 88 Antworten
letzter Beitrag von JeeK am
Diskussion C und Pascal - OT aus 16-Bit Zahl auf Bereich prüfen
- Krill
- Erledigt
-
-
Ich habe mir mal den cc65 angesehen. Aber der erzeugte Code war so schlimm, dass ich das lieber in ASM schreibe. Ausserdem finde ich dass es in ASM (in weiten Teilen) eigentlich nicht viel schwieriger ist als in einer anderen Sprache. Ich habe mir eine Menge an Hilfsfunktionen geschrieben und damit kann ich jetzt relativ gut arbeiten.
-
Ich habe mir mal den cc65 angesehen. Aber der erzeugte Code war so schlimm, dass ich das lieber in ASM schreibe. Ausserdem finde ich dass es in ASM (in weiten Teilen) eigentlich nicht viel schwieriger ist als in einer anderen Sprache. Ich habe mir eine Menge an Hilfsfunktionen geschrieben und damit kann ich jetzt relativ gut arbeiten.
Deswegen ja auch "Pascal-artige" Hochsprache, nicht C. Die von C definierte abstrakte Maschine lässt sich nicht gut auf 6502 abbilden, bei Pascal sieht's dann schon deutlich passender aus.
Und schwieriger oder leichter ist Assembler ja auch nicht (das hängt ganz vom Übungsgrad des Eintippenden ab), aber eben unhandlicher für komplexe Top-Level-Logik mit Menüs und allem.
-
Ich habe mir mal den cc65 angesehen. Aber der erzeugte Code war so schlimm, dass ich das lieber in ASM schreibe. Ausserdem finde ich dass es in ASM (in weiten Teilen) eigentlich nicht viel schwieriger ist als in einer anderen Sprache. Ich habe mir eine Menge an Hilfsfunktionen geschrieben und damit kann ich jetzt relativ gut arbeiten.
Ich habe mir schon einige Sachen mit dem CC65 geschrieben. Das geht einfach schneller und einfacher als Assembler. Ich habe das aber auch schon mit Assembler kombiniert. Da der CC65 einen richtigen Linker besitzt und auch Libraries unterstützt, kann man einfach C und Assembler-Module zusammen linken. Da ist man schon ziemlich dicht an der Hochsprachenentwicklung auf größeren Systemen. Der erzeugte Code ist mir völlig egal, solange die Geschwindigkeit und die Programmgröße ok sind.
-
Ich hab mir das beim C64 zur Grundeinstellung gemacht, nach bestem Wissen und Gewissen ein bisschen auf Platzersparnis und Performance zu achten. Also z.B. hätte ich in Post #17 die Zeilen 9 (BCC) und 10 (BEQ) auch vertauschen können, benötigt dann aber durchschnittlich 1 Tz mehr (BCC:BEQ = 3,5 / BEQ:BCC = 4,5). Das ist ungefähr so, wie beim Abfahren von einer Landstraße auch dann zu blinken, wenn weit und breit niemand zu sehen ist.
In meiner Asm-Bibliothek hatte ich jetzt aber auch eine zum hiesigen Thread-Thema ähnliche Routine, die ich vor 30 Jahren gemacht hatte, nun zum Einsatz kommen sollte, und aber gar nicht richtig funktioniert: Eine 16bit-Zufallszahl zwischen MIN und MAX. Da muss ich also noch mal dran.
-
Ausserdem finde ich dass es in ASM (in weiten Teilen) eigentlich nicht viel schwieriger ist als in einer anderen Sprache.
Das sehe ich aber schon so. In einer anderen Sprache muss ich nicht bei jeder Prozedur/Funktion schauen, ob die evtl. die Prozessorregister (A,X,Y) ändert. ASM ist in der Regel sehr unübersichtlich und daher mMn schon schwieriger.
-
Die von C definierte abstrakte Maschine lässt sich nicht gut auf 6502 abbilden, bei Pascal sieht's dann schon deutlich passender aus.
Magst du das noch etwas ausführen?
-
Vor allem auch, warum das bei Pascal passender aussieht.
-
Die von C definierte abstrakte Maschine lässt sich nicht gut auf 6502 abbilden, bei Pascal sieht's dann schon deutlich passender aus.
Magst du das noch etwas ausführen?
Hier sind ein paar der wichtigsten Punkte aufgeführt.
Mit am relevantesten ist die Vorgabe, dass Funktionen reentrant sein müssen. Da es ja nur 3 Prozessor-Register gibt, erzwingt das sehr schnell einen Stack für lokale Variablen und geradezu zwangsläufig Stack-relative Adressierung. Letzteres gibt es auf 6502 nicht so direkt (und der Stack ist ziemlich klein), und es muss auf einen Software-Stack zurückgegriffen werden.
Ein weiteres Problem ist die nicht sehr starke Typisierung und damit einhergehende implizite Typumwandlung. Da fängt es dann schon damit an, dass ein int mindestens 16 Bits fassen muss, und alle kleineren Integer-Typen beim Rechnen unter der Haube erst mal zu ints gemacht werden.
Es gibt auch keine standardisierte Methode, um Interrupt-Routinen zu definieren, welche man für nichttriviale 6502-Programme recht schnell benötigt.
Und noch viele weitere Punkte. Es schimmert immer wieder die PDP-11 als ursprüngliche Zielmaschine von C durch, zusammen mit diversen Einschränkungen für maximale Portierbarkeit auf andere (größere/spätere) Maschinen. Die 6502-Architektur ist ganz klar weniger mächtig als das.
Vor allem auch, warum das bei Pascal passender aussieht.
Bei Pascal dagegen sind Funktionen (bzw. Prozeduren) per Default nicht reentrant (und haben weiterhin eine feste Anzahl von Parametern), so dass ein Softwarestack vermieden werden kann.
Die Typisierung ist stärker, erlaubt auch die Definition von Wertebereichen und gibt dem Compiler damit mehr Information zur Optimierung.
Pascal ist generell weniger stark standardisiert, und Pascal-Compiler erlauben typischerweise die Definition von Interrupt-Prozeduren.
Nun muss mensch sich aber nicht auf standardkonformes C festlegen, und es wurden Dinge wie microC erfunden.
Sprachen, die Untermengen von C sind (und ein paar Erweiterungen à la Embedded C mitbringen), können passgenauer für 6502 sein.
-
Pascal-Prozeduren nicht reentrant? Wie funktionieren dann Rekursionen?
Interrupt-Routinene würde ich auf jeden Fall in Assembler schreiben.
-
Pascal-Prozeduren nicht reentrant? Wie funktionieren dann Rekursionen?
"Per Default nicht reentrant" im Gegensatz zu "erzwungen immer reentrant".
Es gibt eine Direktive, um Prozeduren explizit reentrant zu deklarieren, und dann nutzen sie auch einen (Software-)Stack.
-
So eine Direktive habe ich noch nie gesehen. Von welchem Pascal sprechen wir?
-
Das sehe ich aber schon so. In einer anderen Sprache muss ich nicht bei jeder Prozedur/Funktion schauen, ob die evtl. die Prozessorregister (A,X,Y) ändert. ASM ist in der Regel sehr unübersichtlich und daher mMn schon schwieriger.
Mit den Registern habe ich kein Problem. Da der 64er ja nur drei hat, gehe ich in der Regel davon aus, dass komplexere Funktionen alle Register trashen. Bei kleineren Funktionen schaue ich im Zweifel nach, das stimmt.
Wo es IMO wirklich haarig wird, ist die Übersicht zu behalten, welche Speicherstellen wo verändert werden. Beim meinem Spriteeditor habe ich anfangs ein paar generische TMP Variablen benutzt, aber das schnell wieder fallen gelassen, weil man dann den Überblcik verliert ob durch den Aufruf einer Funktion vielleicht doch der Wert zwischenzeitlich verändert wird.
Bei hoch optimierten Code muss man sich dass vielleicht antun, wenn man Speicher sparen muss, aber da bin ich zum Glück noch nicht hingekommen. Also konnte ich ganz faul und pragmatisch im Zweifel einfach zusätzliche Variablen verwenden. Dient auch der Übersichtlichkeit.
-
So eine Direktive habe ich noch nie gesehen. Von welchem Pascal sprechen wir?
Such Dir eins aus.
Z.B. Turbo Pascal
Turbo51 hat die $M-Direktive, und IBM hat direkt "REENTRANT".
-
Wo es IMO wirklich haarig wird, ist die Übersicht zu behalten, welche Speicherstellen wo verändert werden. Beim meinem Spriteeditor habe ich anfangs ein paar generische TMP Variablen benutzt, aber das schnell wieder fallen gelassen, weil man dann den Überblcik verliert ob durch den Aufruf einer Funktion vielleicht doch der Wert zwischenzeitlich verändert wird.
Genau das meinte ich eigentlich. Egal, ob Prozessorregister oder andere Variablen (also Speicherstellen eigentlich) - du musst halt immer wieder drauf achten, ob die innerhalb von 10 Zeilen irgendwie verändert wurden. Das ist nervig und auch zeitraubend, weil du dir den Code immer wieder anschauen musst. Meine standardmäßig vielseitig einsetzbare TMP-Variable heißt übrigens dummy.
-
Ich notiere mir im Header jeder Routine, welche Register und welche Temp-Variablen verwendet werden (die Temps benenne ich dann innerhalb der Routine um). Genaugenommen habe ich mir mittlerweile ein Javaprogramm geschrieben, das die Kommentare automatisch schreibt (wer mehr wissen will, hier habe ich das mal beschrieben: https://acoustic-velocity.com/blog/2019/11/09/code-analysis/). Für haarige Fälle malt das Programm sogar Call-Graphen, das habe ich aber letztlich kaum benötigt bislang.
-
die Temps benenne ich dann innerhalb der Routine um
Erstens: wie das?
Zweitens: warum?
-
Wo es IMO wirklich haarig wird, ist die Übersicht zu behalten, welche Speicherstellen wo verändert werden. Beim meinem Spriteeditor habe ich anfangs ein paar generische TMP Variablen benutzt, aber das schnell wieder fallen gelassen, weil man dann den Überblcik verliert ob durch den Aufruf einer Funktion vielleicht doch der Wert zwischenzeitlich verändert wird.
Genau das meinte ich eigentlich. Egal, ob Prozessorregister oder andere Variablen (also Speicherstellen eigentlich) - du musst halt immer wieder drauf achten, ob die innerhalb von 10 Zeilen irgendwie verändert wurden. Das ist nervig und auch zeitraubend, weil du dir den Code immer wieder anschauen musst. Meine standardmäßig vielseitig einsetzbare TMP-Variable heißt übrigens dummy.
Überladung (Overlays) von globalen Variablen sollte vermieden werden, bis sie aus Speicherplatzgründen unvermeidbar wird.
Die Zeropage wurde ja eigentlich auch als erweiterter Registersatz konzipiert, und man kann z.B. so etwas machen, ohne sich um Register-Umbenennung oder versehentliches Überschreiben durch Unterroutinen Sorgen machen zu müssen (64tass):
-
die Temps benenne ich dann innerhalb der Routine um
Erstens: wie das?
Zweitens: warum?
Ich verwende ca65, der lokale Scopes erlaubt (Acme kann das aber z.B. auch). Da kann ich innerhalb des Scopes einfach "schlauer_name = tmp1" schreiben, und dann mit "schlauer_name" weiterarbeiten. Warum: erstens wird der Code verständlicher, weil ja "tmp1" in jeder Routine eine andere Bedeutung hat. Zweitens macht es es mir leicht, wenn ich doch mal auf eine andere Temp-Variable ausweichen muss, denn dann muss ich nur die Zuweisung am Anfang der Routine anpassen.
-
So eine Direktive habe ich noch nie gesehen. Von welchem Pascal sprechen wir?
Such Dir eins aus.
Z.B. Turbo Pascal
Turbo51 hat die $M-Direktive, und IBM hat direkt "REENTRANT".
Dafür hat cc65 doch auch #pragma static-locals: https://cc65.github.io/doc/cc65.html#pragma-static-locals
Zitat7.16 #pragma static-locals ([push,] on|off)
Use variables in the bss segment instead of variables on the stack. This pragma changes the default set by the compiler option --static-locals. If the argument is "on", local variables are allocated in the BSS segment, leading to shorter and in most cases faster, but non-reentrant code.