CHRGET

Aus C64-Wiki
(Weitergeleitet von CHRGOT)
Zur Navigation springenZur Suche springen

CHRGET ist bei Commodore-Computern eine Maschinensprache-Unterroutine, die eine zentrale Rolle für den BASIC-Interpreter spielt. Sie liest Zeichen oder Token fortschreitend im sogenannten Text eines Programms. Also einer Eingabezeile im Direktmodus oder eines gespeicherten BASIC-Programms.

CHRGET beim C64[Bearbeiten | Quelltext bearbeiten]

Name: CHRGET / CHRGOT
Beschreibung: Zeichen aus BASIC-Text lesen
Einsprungpunkt: $73 / 115 (CHRGET)
$79 / 121 (CHRGOT)
Übergebene Argumente:
Rückgabe-Werte:
Akkumulator: Eingelesenes Zeichen
Carry-Flag: 0 = Dezimalziffer
Zero-Flag: 1 = Anweisungsende

CHRGET-Kopie[Bearbeiten | Quelltext bearbeiten]

CHRGET liegt beim C64 im Addressbereich 115-138 ($73-$8A). Beim Kaltstart wird die CHARGET-Routine mit Hilfe der Programmschleife im KERNAL beginnend bei 58336 ($E3E0) aus dem Bereich 58274-58297 ($E3A2-$E3BE) in die Zeropage kopiert. Dies sorgt für eine schnellere Abarbeitung, ist aber auch notwendig, weil CHRGET selbstmodifizierenden Code enthält: Der Zeiger 122/123 ($7A/$7B) auf den Text liegt innerhalb der Routine und wird von ihr selbst verändert.

Details[Bearbeiten | Quelltext bearbeiten]

Es gibt zwei Einsprungstellen:

  • CHRGET (115 = $0073) liest das nächste Zeichen, wobei zuvor der Textzeiger erhöht wird.
  • CHRGOT (121 = $0079) liest das Zeichen an der aktuellen Position des Textzeigers.

Die Routine überliest vorerst alle Leerzeichen (Char 32 = $20) und überträgt dann das eingelesene Zeichen in den Akkumulator. Dabei werden numerische Zeichen (Charactercode 48 = $30 bis 57 = $39) durch ein gelöschtes Carry-Flag gekennzeichnet. Das Zero-Flag kennzeichnet dabei das Anweisungsende, nämlich wenn

  • entweder ein Doppelpunkt-Zeichen (Charactercode 58 = $3A) gefunden wurde, was das Ende der BASIC-Anweisung bedeutet,
  • oder wenn der Wert 0 angetroffen wurde, der das Ende des Eingabepuffers bzw. das Zeilenende anzeigt. Die folgende Tabelle zeigt die möglichen Schlussfolgerungen, auf die Abfrage der fett hervorgehoben Werte:
Carry-Flag Zero-Flag Ergebnis
0 x Ziffer
(Zero-Flag ist hier nie gesetzt)
1 0 keine Ziffer (aber kein Anweisungsende)
1 1 Anweisungsende


Der Textzeiger[Bearbeiten | Quelltext bearbeiten]

Der Textzeiger $7A/$7B wird abhängig davon, ob der Direktmodus oder der Programm-Modus aktiv ist, initialisiert:

  • Im Direktmodus setzt die BASIC-ROM-Routine ab 42112 = $A480 (Eingabewarteschleife) den Zeiger auf $01FF vor den Eingabepuffer (512-600 = $0200-$0258).
  • Im Programm-Modus setzt die BASIC-ROM-Routine ab 42638 = $A68E, die von RUN oder LOAD aufgerufen wird, den Zeiger direkt vor den BASIC-Anfang, der vom Vektor 43/44 ($2B/$2C) bestimmt wird. Im Normalfall (Anfang = $0801) also auf $0800.

Listing[Bearbeiten | Quelltext bearbeiten]

0073:  E6 7A       INC $7A     ; Textzeiger erhöhen, niederwertiger Teil
0075:  D0 02       BNE $0079   ; Überlauf
0077:  E6 7B       INC $7B     ; Textzeiger, höherwertiger Teil
0079:  AD 00 08    LDA $0800   ; Text lesen, mit selbstmodifizierter Adresse 
007C:  C9 3A       CMP #$3A    ; ":"=Anweisungsende, auch 1. Zeichen nach "9"    
007E:  B0 0A       BCS $008A   ; größer "9", keine Ziffer: Carry-Flag=1 oder Anweisungsende bei ":"
0080:  C9 20       CMP #$20    ; Leerzeichen ...
0082:  F0 EF       BEQ $0073   ; überlesen
0084:  38          SEC	       ; Subtraktion vorbereiten ...
0085:  E9 30       SBC #$30    ; Ziffer "0"
0087:  38          SEC         ; Carry invertieren ... (Ziffern sind >=0)
0088:  E9 D0       SBC #$D0    ; Subtraktion retour, kleiner "0", Carry-Flag=1
008A:  60          RTS         ; Zero-Flag=1 Anweisungsende, Carry-Flag=1 keine Ziffer

Hier ist der Textzeiger mit $0800 dargestellt.

Zeitersparnis durch Platzierung in der Zeropage[Bearbeiten | Quelltext bearbeiten]

Der Platz in der Zeropage ist besonders wertvoll, da zahlreiche Assembler-Befehle mit indirekter Adressierung auf die Zeropage angewiesen sind und Zugriffe auf die Zeropage oft schneller sind, als andere Zugriffe. Die CHRGET-Routine belegt fast 10 % der Zeropage, es sollte also wohl begründet sein, diese hier abzulegen. Da Selbstmodifikation auch außerhalb der Zeropage funktioniert (beispielsweise in der erweiterten Zeropage), ist die schnellere Ausführungszeit hier maßgeblich.

Einen Geschwindigkeitsgewinn liefern der selbst modifizierten Zugriff auf den Speicher mit Hilfe der Absoluten Adressierung bei $0079, wo gegenüber einem Code außerhalb der Zeropage ein bis zwei Taktzyklen eingespart werden und zudem das Y-Register unbeschadet bleibt, was dem aufrufenden Code mehr Flexibilität beim Zugriff auf den umliegenden BASIC-Text gewährt. Eine Routine außerhalb der Zeropage müsste dann entweder

  1. die indirekte, Y-nachindizierte Zeropage-Adressierung nutzen und dabei Y-Register zwingend auf 0 setzen, was die Laufzeit zusätzlich verlangsamen würde und als (nicht unbedingt negative) Nebenwirkung die Manipulation des Y-Registers mit sich brächte oder
  2. die Routine unverändert lassen, wobei man hinsichtlich Laufzeit zwar rund 1 Taktzyklus wegen des länger dauernden INC-Befehls in $0073 und $0077 verliert. Der große Nachteil speziell für den aufrufenden Code wäre, dass die Manipulation oder der direkte Zugriff im Interpreter an rund 20 Stellen[1] nicht mehr über indirekt-indizierte Zugriffe erfolgen kann und damit auch zeitintensiver umcodiert werden müsste, da der CHRGET-Zeiger dann nicht mehr in der Zeropage läge und dafür eine Adressierungsart "indirekte Adressierung" für die notwendigen Befehle nicht existiert.

Durch Analyse von typischen BASIC-Programmen erhält man, dass etwa 53 % aller Zeichen in BASIC-Programmen kleiner $3A sind und Leerzeichen nahezu nie vorkommen (String-Konstanten werden nicht mit CHRGET ausgewertet). Mit diesen Werten kann man die Zeitersparnis auf etwa 3 % berechnen.

Das mag auf den ersten Blick viel aussehen, man muss aber bedenken, dass die CHRGET-Routine ihrerseits nur etwa 3 % der Ausführungszeit eines BASIC-Programms ausmacht. Die Einsparung liegt also bei etwa 0,1 %: Bei einem BASIC-Programm, welches einen ganzen Tag läuft, spart man letzten Endes etwa eine Minute. Alles in Allem ist es zweifelhaft, ob die Entscheidung, die CHRGET-Routine in die Zeropage zu verlagern wirklich gerechtfertigt war.

Alternative Implementierungen können hier durchaus deutlich messbare (wenn auch nicht immer merkbare) Beschleunigung erreichen.[2]

CHRGET beim C128[Bearbeiten | Quelltext bearbeiten]

Beim C128 enthält die Routine auch Code zum Umschalten auf das BASIC-RAM in Bank 0 und das anschließende Reaktivieren des ROMs. Des Weiteren wird die Routine nicht in die Zeropage kopiert (sie benötigt immerhin 30 Bytes), sondern in den Speicherbereich ab $0380. Damit dennoch der Textzeiger effizient über die Zeropage manipuliert werden kann, wird hier auf den selbstmodifizierenden Code verzichtet und die Adressierung indirekt Zeropage Y-nachindiziert eingesetzt, wobei als Nebenwirkung das Y-Register nach dem Aufruf stets auf Null gesetzt ist, was von der aufrufenden Ebene zum Vorteil genutzt werden kann.

Details[Bearbeiten | Quelltext bearbeiten]

Wie auch bei der C64-Variante gibt zwei Einsprungstellen:

  • CHRGET (896 = $0380) liest das nächste Zeichen, wobei zuvor der Textzeiger erhöht wird.
  • CHRGOT (902 = $0386) liest das Zeichen an der aktuellen Position des Textzeigers.

Textzeiger[Bearbeiten | Quelltext bearbeiten]

Der Zeiger $3D/$3E in der Zeropage beinhaltet die aktuelle Position auf den BASIC-Text.

Listing[Bearbeiten | Quelltext bearbeiten]

0380:  E6 3D	    INC $3D 	; Textzeiger erhöhen
0382:  D0 02	    BNE $0386
0384:  E6 3E	    INC $3E
0386:  8D 01 FF	    STA $FF01	; Bank 0 einblenden
0389:  A0 00	    LDY #$00
038B:  B1 3D	    LDA ($3D),Y	; Speicher an Textzeigerposition auslesen
038D:  8D 03 FF	    STA $FF03	; Bank 0 wieder ausblenden
0390:  C9 3A	    CMP #$3A
0392:  B0 0A	    BCS $039E
0394:  C9 20	    CMP #$20
0396:  F0 E8	    BEQ $0380
0398:  38	    SEC
0399:  E9 30	    SBC #$30
039B:  38	    SEC
039C:  E9 D0	    SBC #$D0
039E:  60	    RTS

Abgesehen von den dokumentierenten Abweichungen gilt das Gleiche wie beim C64 CHRGET-Listing.

Wedge[Bearbeiten | Quelltext bearbeiten]

Da CHRGET benutzt wird, um jedes BASIC-Zeichen zu lesen, bevor eine Anweisung ausgeführt wird, kann man hier eingreifen, um neue BASIC-Befehle für eine BASIC-Erweiterung hinzuzufügen. Eine solche Änderung wird im Allgemeinen als "Wedge" bezeichnet. Ein bekanntes Beispiel für diese Technik ist die DOS Wedge.

Diese Standardtechnik lässt sich auch zurückgehend bis zum PET verwenden, wo dies die einzige Möglichkeit darstellte, Erweiterungen einzubinden, da erst spätere BASIC-Interpreter (zumindest mit BASIC V2) entsprechende Vektoren besitzen, eine Erweiterung vollständig auf Token-Ebene vorzunehmen.

Weblinks[Bearbeiten | Quelltext bearbeiten]

Quellen[Bearbeiten | Quelltext bearbeiten]

  1. ROM-Listing AAY: Nutzung von $7A (CHRGET-Zeiger) Sprache:englisch
  2. Thema: Prüfung auf Ziffer in CHRGET (C64/C128) auf Forum64.de: Vorschläge für alternative Implementierungen von CHRGET