Hallo Besucher, der Thread wurde 4,6k mal aufgerufen und enthält 36 Antworten
letzter Beitrag von M. J. am
Diskussion JMP relative Adressierung - OT aus Frage zu BNE
- Vernunftmensch
- Erledigt
-
-
absolut
jsr blabla
Im Stack liegt
Also alles nur "Lösungen" mit absoluten Adressen.
Hex-Numeral
Ein Parser parst in der Regel nicht ein einzelnes Token, sondern einen ganzen Tokenstream bis kein weiteres Element mehr gefunden wird, welches zu einem Ausdruck paßt. Dabei werden dann auch die Operatorprioritäten mitberücksichtigt. Eine einfache vierstufige Prioritätenverteilung wie in Pascal erfordert vier Routinen, die sich rekursiv aufrufen: 1.) ausdruck_holen (Ebene der Vergleiche), 2,) einfachen_ausdruck_holen (Ebene von +, -, | und ||), 3.) term_holen (Ebene von *, /, >>, AND) und 4.) faktor_holen (Ebene von Zahlen, Funktionen, sonstigen Bezeichnern, Klammerung und Negierung). Um also ein einfaches Zahlliteral zu parsern, müssen zunächst die Routinen ausdruck_holen, einfachen_ausdruck_holen, term_holen und faktor_holen durchlaufen werden. Als Rückgabewert müßte dann jeweils neben dem Zahlenwert auch die Information "war es eine Hexzahl mit 00 am Anfang" mitgeführt werden. Diese Information wird aber sofort zunichte gemacht, wenn irgendein Operator auftaucht, bzw. das Ergebnis wäre nicht definiert. Was z. B. ist "$00fe + 2"? Wäre dies dann $100 (Wort) oder $0 (Byte)?
Bei Dezimalzahlen im Ausdruck ist das eher problematisch, da wird man nur nach der Größe gehen können (<256 => Byte, >=256 => Word)
[..]
der "größere" Datentyp der Argumente den Ergebnistyp bestimmtGenau das machen die Assembler ja auch. Im vorliegenden Fall soll nur das genaue Gegenteil passieren: Aus dem Wert < 256 soll ein Wort werden. Damit wäre das Verhalten bei Hexzahlen also anders als bei Dezimalzahlen. Syntaktisch halte ich dies für mindestens unsauber.
casts
Genau das meinte ich mit der Kennung ".w" (Wort) und ".b" (Byte), sei es als prefix oder postfix zu einem Ausdruck.
So würde ich es jedenfalls machen, käme ich mal dazu, einen eigenen symbolischen Assembler zu schreiben.
Been there, done that.
-
Naja, der Stack gehoert zum CPU space
Wahlweise nimmst Du 2 ZP Adressen oder springst in den Stack.
Es geht also wunderbar und es ist auch keine Adresse die jemals anders sein wird. Egal wohin der code compiliert wird.
Du darfst Dich gerne einsichtig zeigen -
Als Rückgabewert müßte dann jeweils neben dem Zahlenwert auch die Information "war es eine Hexzahl mit 00 am Anfang" mitgeführt werden.
Ja. Einfach der Typ. Byte oder Word. $0000 hat den Wert 0 und den Typ Word, $00 hat auch den Wert 0 aber den Typ Byte.
Was z. B. ist "$00fe + 2"? Wäre dies dann $100 (Wort) oder $0 (Byte)?
Wort. Weil $00FE bereits den Typ Wort hat und der größere Typ "gewinnen" soll (hier, bei der "+"-Verknüpfung).
Wo das Konzept *eventuell* Probleme machen könnte ist z.B. bei Berechnungen der Art BASE+SIZE*INDEX, wobei SIZE und INDEX jeweils (dezimale) Numerale <256 sind. Also z.B. derart berechnete Tabellen von Bildschirmzeilenanfängen. Da würde die direkte Schreibweise z.B. mit $0400+40*12 dann fehlschlagen, da 40*12 in einem Byte bleibt und damit ungewollt das High-Byte "verliert". Hier kann man sich aber schon damit behelfen, daß vorab SIZE als Symbol mit Worttyp definiert wird. Oder man, alternativ, im multiplikativen Teilausdruck $0400+$0028*12 schreibt.
-
Wahlweise nimmst Du 2 ZP Adressen oder springst in den Stack.
Es geht also wunderbar und es ist auch keine Adresse die jemals anders sein wird. Egal wohin der code compiliert wird.Daß unter Zuhilfenahme fester Adressen der PC des Programms ermittelt werden kann, habe ich vorher selber anhand des Beispiels einer AppleII-Steckkarte dargestellt. Da erzählst Du mir wirklich nichts Neues. Das Problem aber lautete: "Kann man relative Sprünge in einem Programm mit Hilfe von 6502-Befehlen simulieren?" und nicht: "Wie kann man das Problem externalisieren?" Auch Deine "Lösung" verschiebt das Problem nur. Da spielt es keine Rolle, an welche Stelle Du im Speicher irgendwas hinpokest, ob im Stackbereich $100..$1ff oder nach $c000 oder sonstwohin.
Anderes Beispiel: Auf dem x86 sind die CALL-Befehle (Unterprogrammaufruf entsprechend zu JSR) relativ. Hier ist es tatsächlich möglich, mittelsCodekomplett relativen Code zu schreiben (z. B. für eine relokatible Bibliothek). Auf dem 6502 geht dies nicht, und jeder Versuch, es doch irgendwie hinzukriegen entspricht dem Versuch, ein Perpetuum Mobile zu bauen.
Wo das Konzept *eventuell* Probleme machen könnte [..]
Der Haken dabei ist, daß das Verhalten "absolute Adressierung bei Adressen unter 256" eine sehr seltene Ausnahme darstellt. Der Verwendungsgrad dürfte grob geschätzt bei unter 1 Promille liegen. Demgegenüber stehen aber jetzt all die Probleme, die man sich einhandelt, wenn man definiert, daß bei Hexadezimalzahlen $00xx eine absolute Adressierung darstellen soll, mit der andauernden Gefahr, unbeabsichtigt Fehler im Programmtext zu erzeugen, weil die Zahlen von ihrer Schreibweise her anders als mathematisch gewohnt bereits bedeutungstragend sein sollen. Das halte ich für sehr bedenklich. Programmiersprachen sollten dabei helfen, Probleme zu lösen, nicht welche zu erzeugen. Man stelle sich vor, es käme jemand auf die Idee, eine Programmiersprache zu erfinden, bei der Whitespace-Zeichen eine Bedeutung haben...
-
[...] mit der andauernden Gefahr, unbeabsichtigt Fehler im Programmtext zu erzeugen, weil die Zahlen von ihrer Schreibweise her anders als mathematisch gewohnt bereits bedeutungstragend sein sollen.
Bei der Programmierung befindet man sich aber nicht im "luftleeren" Raum der Mathematik, wo Zahlen für sich existieren. In C hat jeder Zahlenwert auch schon einen Typ. Und da sind sicher auch unerwartete Ergebnisse möglich, wenn man den Ausdruck "1/2" schreibt, *eigentlich* 0,5 erwartet, tatsächlich aber 0 herausbekommt!
Allerdings habe ich mir anhand meines eigenen Beispiels oben doch noch mal kurz den Kopf zerbrochen, und tendiere jetzt auch eher dazu, daß *implizites* "Byte"-Typing von Numeralen in der Tat mehr Probleme macht als es löst. O.K.
Der Haken dabei ist, daß das Verhalten "absolute Adressierung bei Adressen unter 256" eine sehr seltene Ausnahme darstellt. Der Verwendungsgrad dürfte grob geschätzt bei unter 1 Promille liegen.
Wo mir das in einem leicht anderen Zusammenhang mal zur Fußfalle geworden ist, ist diese kleine Routine, die ich in Code-Generatoren verwende:
Mein (Lieblings-)Assembler, eben der Inline-Assembler von BBC BASIC, hat leider eben genau auch das Problem, daß er bei Zahlenausdrücken mit Wert <256 die ZP-Adressierung erzwingt. Leider auch dann, wenn die beiden oberen Hexziffern eines Hex-Numerals 0 sind. Tja. Und in solchen Fällen schreibe ich dann eben einen Ersatzwert hin, der garantiert nicht mißverstanden werden kann - die aufrufende Routine initialisiert die Adresse im STA-Befehl ohnehin noch.
Im Programm wird obige Routine dann per Akku mit Opcodes und Operanden-Bytes gefüttert und baut so die gewünschte neue Routine auf.
Man stelle sich vor, es käme jemand auf die Idee, eine Programmiersprache zu erfinden, bei der Whitespace-Zeichen eine Bedeutung haben...
Die gibt's schon: https://de.wikipedia.org/wiki/…pace_(Programmiersprache) ... ... und was ist mit Python?
-
Stack ist aber fix verdrahtet. Beschreibbar ohne sta,ist etc.
6502 spezifisch, nicht systemspezifisch und schon gar nicht eine Adresse im ROM *seufz*.
Aber wenn man es gar nicht erst will, ja, dann gehen Sprünge ohne eine Absolutadresse natürlich nicht -
n C hat jeder Zahlenwert auch schon einen Typ. Und da sind sicher auch unerwartete Ergebnisse möglich, wenn man den Ausdruck "1/2" schreibt, *eigentlich* 0,5 erwartet, tatsächlich aber 0 herausbekommt!
Da gebe ich Dir völlig recht. Ein Grund, warum ich privat kein großer Freund von C bin, auch wenn ich es selbst gebrauche. Wirth hat für Pascal dies dann auch extra so gelöst, daß / eine Fließkommadivision und DIV eine Integerdivision bedeutet.
Und in solchen Fällen schreibe ich dann eben einen Ersatzwert hin, der garantiert nicht mißverstanden werden kann - die aufrufende Routine initialisiert die Adresse im STA-Befehl ohnehin noch.
Diese Schreibweise pflege ich auch, und Du glaubst ja gar nicht, in wievielen (fremden) Programmen ich diese "LDA $ffff" oder "STA $ffff" schon gefunden habe...
und was ist mit Python?
Kenne ich nicht. Nie davon gehört...
Stack ist aber fix verdrahtet. Beschreibbar ohne sta,ist etc.
Es geht nicht darum, was ich persönlich will, aber selbst wenn Du Dir ein Programm auf den Stack pokest mittels
mußt Du den Code nachher auch aufrufen können. Zum Aufruf verwendest Du aber wieder eine absolute Adresse. Das heißt, Du erzeugst Dir an absoluter Position im Speicher ein Programm mit absoluten Adressen, damit das "eigentliche" Programm relativ sein soll. Klar kannst Du das machen, aber es ist keine Lösung im Sinne der Problemstellung, die da lautet: Kann man ohne Verwendung absoluter Adressen relative Sprünge durchführen? Antwort: Nein, kann man nicht. Wie ich schon vorher schrieb, ist eine übliche Vorgehensweise
und danach hat man die PC-Adresse auf dem Stack und kann diese dann nach Herzenslust weiterverarbeiten. Man kann sich auch in der Zeropage den Befehl JMP abs basteln, nach Belieben die Adresse patchen und dann den Befehl anspringen. Alles reichlich bekannte Methoden. Aber es sind halt nur Krücken, die auf absolute Adressierung zurückgreifen, weil es eben ohne eine solche auf dem 6502 nicht geht. (Daß es bei anderen Prozessoren wohl geht, zeigt obiges x86-Beispiel.)
-
... und danach hat man die PC-Adresse auf dem Stack und kann diese dann nach Herzenslust weiterverarbeiten. Man kann sich auch in der Zeropage den Befehl JMP abs basteln, nach Belieben die Adresse patchen und dann den Befehl anspringen. Alles reichlich bekannte Methoden. Aber es sind halt nur Krücken, die auf absolute Adressierung zurückgreifen, weil es eben ohne eine solche auf dem 6502 nicht geht. (Daß es bei anderen Prozessoren wohl geht, zeigt obiges x86-Beispiel.)
Stimmt natürlich mit der absoluten Adressen/Adressierung. Aber man kann es auch anders betrachten. Wenn man sich die Zeropage als Registersatz denkt, mit Registernummer 00 bis FF, dann sind das Referenzen, die so inhärent gesehen werden können, wie Opcodes oder wären vergleichbar mit einer Sprungleiste im ROM (die ebenso stets absolut zu sehen ist).
Ich hab so eine kleines Testprogramm gemacht, das in Anlehnung an den 65816 die Befehle BRL (BRanch Long) , PER (Push Effective Relative und BSR (analog zum 6809-Befehl LBSR Long Branch Subroutine - beim 65816 gibt es den auch nicht direkt und er müsste mit PER und BRL "nachgebastelt" werden) emuliert. Nur als Machbarkeitsstudie, richtig zu gebrauchen ist es nicht, weil es die Register A, X, Y nicht erhält. Es passiert alles zur Laufzeit und ein solcher neuer "Opcode" braucht 5 Bytes. Ein Runtime-Linker wäre hier sicher effizienter.
Tatsächlich im Code fix verankert sind freilich die besagten Registernummern (=Zeropageadressen). Das ist das Einzige, auf das sich das Programm bezieht.Zur Spitze treiben könnte man es auch so, indem man BRK als Software-Interrupt ansieht und dann BRK+Opcode(BRL, PER, BSR)+Operand (rel. Adresse) einsetzt. Damit wird man auch die absolute Adressierung als solche los. Es müsste nur der BRK-Vektor umgebogen werden und natürlich werden entsprechende Zeropage-Register Verwendung finden, was wie in obiger Sichtweise vertreten, nicht als absolute Adressierung gewertet werden müsste.
Das war mir dann aber fürs schnelle Probieren doch zu aufwändig (mit noch mehr Stack-Manipulationen ....).Aber man kann das Programm an jede beliebige Stelle des Adressbereichs kopieren und über die Anfangsadresse starten. Also so gesehen verhält es sich als ein "position indepentend code".
Wer mag, kann sich das freilich auch ansehen:
relative.asm
relative.prg (mit Ladeadresse $C000 bzw. 49152) -
Ich verstehe im Prinzip, wie Du das meinst, muß jedoch folgendes anmerken:
Wenn man sich die Zeropage als Registersatz denkt
Kuz gesagt: Hiermit veränderst Du die Definition der CPU. Eine Speicherung von Daten an eine absolute Adresse wird dadurch verdeckt, indem die Grenzen, in denen die CPU ursprünglich definiert ist, ausgeweitet werden und die CPU sich die Zeropage quasi einverleibt.
Natürlich kann man die CPU auf dieser neuen Ebene denken. Wieso auch nicht? Letztendlich ist in der Informatik alles stets eine Frage der Wahl der Betrachtungsebene, von denen es nahezu unzählig viele gibt (vgl. auch z. B. die Diskussion um "Emulation vs. FPGA vs. Originalchips"). Diese Ebenen erstrecken sich reduktionistisch von einem "niederen" Ende ("in den Leiterbahnen wandern die Elektronen") bis zu einem holistischen Ende "höherer Ordnung" ("mein Computer erzählt mir märchenhaft in bunten Bildern von Sams abenteuerlicher Reise").
Aussagen wie "Relative Adressierungen sind nicht möglich" können sich stets nur auf eine konkrete Ebene beziehen, so wie Sam auch nur auf einer Ebene existiert und auf der anderen lediglich Stromimpulse ihr Unwesen treiben. Die von mir für die Aussage gewählte Ebene ist die, in der der Prozessor als eigenständige Einheit gegenüber Speicher und anderen Geräten definiert ist. Verrutscht man diese Ebene in die Richtung von Sam, so daß Elemente nach dem Motto "die Summe ist mehr als die Einzelteile" eine neue Bedeutung annehmen, kann man sagen "die Zeropage ist ein Registersatz" oder "Romadressen sind inhärenter Teil des Systems (einer höheren Ebene)". Das geht dann weiter in die Richtung Deines Beispiels, wo man neue Befehle für den Prozessor definiert. Als nächstes folgt dann threaded Code und Bytecode, irgendwann Scriptsprachen oder Lisp-Listen oder ... Die Basis auf der niedrigsten Ebene ist dabei stets die gleiche (Materie), doch existieren auf einer anderen Ebene dann relative Sprünge und auf noch einer anderen Objektmethoden oder irgendwas anderes.
Lange Rede, kurzer Sinn: Nein, Dein Vorschlag löst das Problem leider auch nicht auf der Ebene, für die die Aussage getroffen wurde.die Register A, X, Y nicht erhält
Was hindert Dich daran, die Register kurz zwischenzuspeichern?
-
Kuz gesagt: Hiermit veränderst Du die Definition der CPU. Eine Speicherung von Daten an eine absolute Adresse wird dadurch verdeckt, indem die Grenzen, in denen die CPU ursprünglich definiert ist, ausgeweitet werden und die CPU sich die Zeropage quasi einverleibt.
Natürlich kann man die CPU auf dieser neuen Ebene denken.
[philosophischer Exkurs]Lange Rede, kurzer Sinn: Nein, Dein Vorschlag löst das Problem leider auch nicht auf der Ebene, für die die Aussage getroffen wurde.
Naja, "kurz gesagt" ist das ja nicht gerade. Man muss ja nicht gleich eine philosophische Erörterung des Themas hier lancieren, um den Standpunkt recht vehement zu verteidigen. Ich will auch hier vielleicht nicht unbedingt das "Problem auf der Ebene" lösen. Für jene, denen es brauchbar und angemessen erscheint (da können andere vom zitierten Autor durchaus anderer Meinung sein), soll es schlicht weg ein Ansatz oder ein Quell einer Idee darstellen, mit dem Problem dennoch umzugehen, auch wenn die Realisierung schlussendlich nicht in Hardware ist. Und ich orientiere mich an der gesamten Diskussion und nicht nur an dem initialen Posting, d.h. es soll doch erlaubt sein auch Ideen, Vorschläge, Wünsche aufzuschnappen, die andere hier eingebracht haben. Das muss man durchaus nicht "initiales-posting-zentriert" sehen, solange man nicht OT wird.
Was hindert Dich daran, die Register kurz zwischenzuspeichern?
Darauf wär ich ja jetzt gar nicht gekommen .... nein, im Ernst: Ich überlasse es dem geneigten Leser sich daran zu versuchen. Ich habe nur schon angedeutet, dass es bei BSR aufwändiger werden könnte, da es vernünftiger wäre, dass auch am Stack zu machen, um die Verschachtelungseigenschaft zu erhalten. Bei BRL und PER nicht so ein Thema, aber auch da wäre es schön, wenn die voneinander entkoppelt sind und reentrant bleiben, was ebenfalls den Stack oder einen anderen als Stack genutzten Sicherungsbereich erforderlich machen würde.
-
Naja, "kurz gesagt" ist das ja nicht gerade.
Das war schon recht kurz gesagt. Sonst gehen Vorträge eigentlich über 90 Minuten oder mindestens so lange, bis alle eingeschlafen sind. Es ging auch mitnichten darum, Deine Programmiertechnik per se zu kritisieren. Über ähnliche Routinen haben wir ja schließlich alle mal nachgedacht. Ist doch schön, daß Du Deine Routinen hier für Andere zur Verfügung stellst.
Und ich orientiere mich an der gesamten Diskussion und nicht nur an dem initialen Posting, d.h. es soll doch erlaubt sein auch Ideen, Vorschläge, Wünsche aufzuschnappen, die andere hier eingebracht haben. Das muss man durchaus nicht "initiales-posting-zentriert" sehen, solange man nicht OT wird.
Klar, aber Du hattest Dich mit Deinem Post direkt an mich gewandt, also hast Du eine Antwort von mir bekommen, und daß ich nicht für die anderen hier spreche (zum Glück ), dürfte klar sein.
da es vernünftiger wäre, dass auch am Stack zu machen, um die Verschachtelungseigenschaft zu erhalten.
Das hatte ich mir auch überlegt, bin aber nicht sicher, ob man dies unbedingt über den Stapel machen muß. Ich hätte die Register entweder zu Beginn in der Zeropage oder per Selbstmodifikation gesichert und vor dem RTS restauriert
Code- dobsr:
- sta a
- stx x
- sty y
- tsx ; stack pointer
- lda $0101,x ; address-1 low following the jsr per
- sta templ
- lda $0102,x ; address-1 high following the jsr per
- sta temph
- ldy #1 ; position to right after the jsr per
- clc ; correct return address by one
- lda (templ),y ; offset low from following the jsr per
- adc $0101,x ; giving absolute address-1
- pha ; just a dummy for the high byte stored later
- pha ; the low byte
- iny
- lda (templ),y ; same for high byte
- adc $0102,x ; stackoffset +2, with 2 bytes more on stack
- sta $0100,x ; high byte overwrites previous dummy byte
- ; absolute subroutine address first on stack
- ; in RTS format
- clc
- lda templ ; old return address
- adc #2 ; skip word following jsr bsr (offset value)
- sta $0101,x ; low byte the last on stack
- lda temph
- adc #0
- sta $0102,x ; high byte of return address
- ldy y
- ldx x
- lda a
- rts ; pull and jump to the subroutine address
So ist der Code auch reentrant (die dobsr-Routine gilt als atomar). Oder mache ich hier einen Denkfehler? Schwieriger würde es, wenn man auch das Statusregister retten will, da man dies über PHP PLP erledigen muß, so daß man die Position der Rücksprungsadresse auf dem Stack anders berechnen muß. Oder man holt es sich zu Beginn per PHP PLA und schreibt es am Ende mittels LDA # PHA PLP zurück (ist aber langsamer).
-
So ist der Code auch reentrant (die dobsr-Routine gilt als atomar).
Hier muss ich was verpasst haben. Wieso sollte das atomar sein?
-
Wieso sollte das atomar sein?
Nur in dem Sinne, daß die Routinen zur Umsetzung der neuen Befehle ohne Unterbrechung durchgeführt werden, d. h. weder sich selbst aufrufen, noch von Unterbrechungprogrammen ausgeführt werden. Die Verwendung dieser Befehle im Interrupt sollte sich verbieten, weil dies nur zu unnötigen Komplikationen führt.
-
Richtig, das mit der Verschachtelung bei BSR war mein Denkfehler. Es reichen 3 fixe Speicherstellen, wenn man mal davon ausgeht, das keine preemtive Multitasking Umgebung vorliegt oder gar in einer ISR diese Konstrukte aufgerufen werden.
-
Nachdem ich den Eindruck hatte, dass man etwas "Fertiges" im Sinne von Brauchbares verlangt, hab ich die "Studie" etwas erweitert und abgerundet:
Die Idee mit der Sicherung der Register von M.J. hab ich aufgegriffen.
Es gibt die Option mit usemacro=1 die Funktionen mit ACME-Makros zu nutzen, z.B.
+brl destination
+bsr subroutine
+per address
Weiters gibt es die Option reentrant (zur Assemble-Time), mit der man zugegebenermaßen brutal die Funktionen "atomic" machen kann (indem einfach IRQs unterdrückt werden).
Die Aufbau ist so geändert worden, dass man das Ding leichter als Include nutzen kann oder den Hauptcode dort inkludieren kann (am Ende).relative.asm
relative.prg (ab $C000, kann aber wie gesagt überall liegen) -