![]() |
|
Start | Super Packer | Atari Graphics Studio | Graph2Font | Mads | MadPascal | YouTube | http://madteam.atari8.info |
Bewesoft
Syzygy #8
tłumaczył: Lizard/Aids
Często pisząc programy w asemblerze nie zastanawiamy się nad ich długością. (Kto przejmuje się
tym, że edytor ma 24999B na tekst zamiast 25000...)
O PROBLEMIE OGÓLNIE
Najlepsze rezultaty podczas skracania programów
osiąga się poprzez zmianę algorytmu. Inaczej mówiąc,
należy przyjżeć się podprogramom, które mogą być
wyłączone i spróbować napisać część programu
w inny, lepszy sposób.
STOSUJ PODPROGRAMY! Najprostszą metodą na skrócenie programu jest stosowanie procedur tak często jak to możliwe. Gdy dwa kawałki procedury wyglądają tak samo, a różnią się tylko jednym szczegółem, to dobrze jest umieścić taki kawałek w podprogramie. Nie bójcie się używać krótkich podprogramów! Np.: LDA #1 | LDA #1 CLC | JSR ADDIT ADC INDEX | ... STA INDEX | LDA #20 ... | JSR ADDIT LDA #20 | ... CLC | ADDIT CLC ADC INDEX | ADC INDEX STA INDEX | STA INDEX ... | RTS (7 bajtów) | (5 plus 6 bajtów)
Po czterech wywołaniach procedury ADDIT zyskujemy 2 bajty na każde następne wywołanie.
ZMIENNE
Umieszczajcie jak najwięcej zmiennych na stronie
zerowej. Zyskacie po jednym bajcie przy każdorazowym odwołaniu do zmiennej i dodatkowo nie
trzeba używać starszego bajtu do określenia adresu
takiej zmiennej.
LDA #0 | LDA #0 STA VAR | STA VAR+1 ... | ... LDA VAR | VAR LDA #0 STA $D01A | STA $D01A ... | VAR DTA B(0) | (12 bajtów) | (10 bajtów) Używając zmiennych jako flag można zaoszczędzić wiele miejsca używając tylko jednego bitu i obsługiwać ją poprzez rozkazy: LSR, ROR i BIT: * Zeruj flagę | * Zeruj flagę LDA #0 | LSR FLAG LSR FLAG | STA FLAG | * Ustaw flagę | * Ustaw flagę LDA #1 | SEC STA FLAG | ROR FLAG * Testuj flagę | * Testuj flagę LDA FLAG | BIT FLAG BNE LABEL | BMI LABEL (12 bajtów) | (9 bajtów)
Zwróćcie uwagę, że pokazana metoda nie zmienia akumulatora, tak więc można zaoszczędzić jeszcze więcej miejsca usuwając rozkazy: PHA i PLA. Znacznik C będzie zmieniany po każdym ustawieniu/skasowaniu flagi.
SKOKI Znając stan znaczników zastępujcie skoki bezwzględne (JMP) warunkowymi (B??). Oczywiście wtedy, gdy skok odbywa się gdzieś niedaleko: LDA TEXT,X | LDA TEXT,X ORA #$80 | ORA #$80 STA SCREEN,Y | STA SCREEN,Y JMP LABEL | BMI LABEL TEXT DTA C'Text!' | TEXT DTA C'Text!' LABEL ... | LABEL ... (16 bajtów) | (15 bajtów) Nigdy nie używaj rozkazu RTS za JSR: JSR LABEL | JMP LABEL RTS | (4 bajty) | (3 bajty)
Gdy podprogram jest dostatecznie blisko, można zastosować skok warunkowy dla zyskania jednego bajtu, a jeśli to możliwe umieścić cały podprogram zamiast powyższego przykładu - zysk 4 bajtów!
CMP #1 | CMP #1 BNE LABEL1 | BEQ LABEL3 JMP LABEL2 | ... LABEL1 ... | RTS RTS | LABEL3 BEQ LABEL2 LABELX ... | LABELX ... LABEL2 ... | LABEL2 ... (8 bajtów) | (7 bajtów) Wadą tej metody jest konieczność wystąpienia rozkazu RTS lub JMP pomiędzy skokami warunkowymi. Nie wszystko jednak stracone, gdy takiego rozkazu nie ma. Drugi skok należy wówczas wstawić w środek programu w miejscu, gdzie warunek zawsze będzie fałszywy: CMP #1 | CMP #1 BNE LABEL1 | BEQ LABEL3 JMP LABEL2 | ... LABEL1 ... | LDA #20 LDA #20 | LABEL3 BEQ LABEL2 STA VAR | STA VAR ... | ... LABEL2 ... | LABEL2 ... (11 bajtów) | (10 bajtów) Do przeskoczenia pojedynczej instrukcji (tylko jedno lub dwubajtowej) nie używajcie skoków. Do tego można wykorzystać rozkaz BIT: CMP #1 | CMP #1 BNE LABEL1 | BNE LABEL1 INX | INX JMP LABEL2 | DTA B($24) LABEL1 DEX | LABEL1 DEX LABEL2 ... | ... (9 bajtów) | (7 bajtów) PUT1 LDA #1 | PUT1 LDA #1 BNE PUT | DTA B($2C) PUT2 LDA #2 | PUT2 LDA #2 PUT ... | PUT ... (6 bajtów) | (5 bajtów)
O co tu chodzi? Można powiedzieć, że pseudo rozkaz "DTA B($24)" powiadamia procesor by przeskoczył
jeden bajt, a "DTA B($2C)" - dwa bajty. Tak naprawdę, procesor wykonuje w tym miejscu rozkaz BIT, którego operandem są przeskakiwane bajty. BIT zmienia tylko znaczniki N, V i Z.
OPERACJE NA SŁOWACH Przy operacjach dodawania/odejmowania bajtu do/od słowa używajcie rozkazu INC/DEC do korekty starszego bajtu: LDA VAR | LDA VAR CLC | CLC ADC #20 | ADC #20 STA VAR | STA VAR LDA VAR+1 | BCC LABEL ADC #0 | INC VAR+1 STA VAR+1 | LABEL ... (13 bajtów) | (11 bajtów) LDA VAR | LDA VAR SEC | SEC SBC #20 | SBC #20 STA VAR | STA VAR LDA VAR+1 | BCS LABEL SBC #0 | DEC VAR+1 STA VAR+1 | LABEL ... (13 bajtów) | (11 bajtów) Zwiększając/zmniejszając słowo o jeden również używajcie rozkazów INC/DEC: LDA VAR | INC VAR CLC | BNE LABEL ADC #1 | INC VAR+1 STA VAR | LABEL ... LDA VAR+1 | ADC #0 | STA VAR+1 | (13 bajtów) | (6 bajtów) LDA VAR | LDA VAR SEC | BNE LABEL SBC #1 | DEC VAR+1 STA VAR | LABEL DEC VAR LDA VAR+1 | SBC #0 | STA VAR+1 | (13 bajtów) | (8 bajtów) Porównując słowa można stosować te same metody co przy odejmowaniu: LDA VAR1+1 | LDA VAR1 CMP VAR2+1 | CMP VAR2 BCC LOW | LDA VAR1+1 BNE HIGH | SBC VAR2+1 LDA VAR1 | BCC LOW CMP VAR2 | HIGH ... BCC LOW | HIGH ... | (14 bajtów) | (10 bajtów)
Można używać tego sposobu używając tylko znacznika C (mniejszy niż lub większy równy - BCC, BCS).
STAŁE Ustawiając więcej niż jedną stałą można próbować używać jednobajtowych rozkazów INX, DEX, LSR, itp.: LDA #0 | LDX #0 STA VAR1 | STX VAR1 LDA #1 | INX STA VAR2 | STX VAR2 (8 bajtów) | (7 bajtów) LDA #1 | LDA #1 STA $30A | STA $30A LDA #0 | LSR STA $30B | STA $30B STA $309 | STA $309 LDA #$80 | ROR STA $308 | STA $308 (18 bajtów) | (16 bajtów) Można też użyć wartości kończącej pętle: LDX #20 | LDX #20 LOOP LDA TEXT,X | LOOP LDA TEXT,X STA SCREEN,X | STA SCREEN,X DEX | DEX BPL LOOP | BPL LOOP LDA #10 | STX VAR2 STA VAR1 | LDA #10 LDA #$FF | STA VAR1 STA VAR2 | (19 bajtów) | (17 bajtów)
ZMIANA POJEDYNCZYCH BITÓW Chcąc zmienić tylko poszczególne bity w bajcie możecie użyć poniższego triku: LDA VAR1 | LDA VAR1 AND #$BC | EOR $D301 STA VAR2 | AND #$BC LDA $D301 | EOR $D301 AND #$43 | STA $D301 ORA VAR2 | STA $D301 | (16 bajtów) | (13 bajtów)
PĘTLE Przy prostych pętlach sterowanych rejestrami X lub Y usuwajcie rozkazy CPX i CPY: LDX #0 | LDX #LEN-1 LOOP LDA SRC,X | LOOP LDA SRC,X STA DST,X | STA DST,X INX | DEX CPX #LEN | BPL LOOP BCC LOOP | (13 bajtów) | (11 bajtów) Powyższy przykład ma sens tylko wtedy, gdy liczba przebiegów nie przekracza 128. Aby osiągnąć to samo dla liczby przebiegów do 255 można zastosować poniższy sposób: LDX #0 | LDX #LEN LOOP LDA SRC,X | LOOP LDA SRC-1,X STA DST,X | STA DST-1,X INX | DEX CPX #LEN | BNE LOOP BCC LOOP | (13 bajtów) | (11 bajtów) No dobrze, ale co zrobić, kiedy licznik może być tylko zwiększany z powodu nakładania się na siebie żródłowego i docelowego obszaru pamięci? Przyjrzyjmy się temu: LDX #0 | OF EQU 256-LEN LOOP LDA SRC,X | LDX #OF STA DST,X | LOOP LDA SRC-OF,X INX | STA DST-OF,X CPX #LEN | INX BCC LOOP | BNE LOOP (13 bajtów) | (11 bajtów)
Jest jeszcze dużo innych konstrukcji pętli.
PARAMETRY ZA ROZKAZEM JSR Gdy podprogram wywoływany jest wielokrotnie, wygodnie jest umieszczać parametry dla niego za jego wywołaniem. Dobrze znanym przykładem jest procedura wypisująca informacje: PRINT PLA STA VAR PLA STA VAR+1 PRT2 INC VAR BNE PRT3 INC VAR+1 PRT3 LDY #0 LDA (VAR),Y BEQ PRT4 JSR PUTCHAR JMP PRT2 PRT4 LDA VAR+1 PHA LDA VAR PHA RTS VAR jest dwubajtową zmienną na stronie zerowej, a PUTCHAR - podprogramem wypisującym jeden znak. Wywołanie PRINT może wyglądać na przykład tak: JSR PRINT DTA C'To będzie wyświetlony tekst!'' DTA 0 Nie tylko tak długie parametry jak teksty można umieszczać za JSR-em: LDA #155 | JSR PUT JSR PUT | DFB 155 (5 bajtów) | (4 bajty) Ponieważ podprogram, do którego przekazuje się parametry tą metodą będzie dosyć długi, to należy użyć takiego wywołania wielokrotnie dla uzyskania efektu. Aby to jeszcze trochę skrócić, wynalazłem następujący podprogram (od tł.: w prawej kolumnie umieszczono krótszą wersję prezentowanego przez Bewesofta podprogramu): GETSTK STX XSAVE | GETSTK STX XSAVE STY YSAVE | STY YSAVE TSX | TSX INX | INC $103,X INX | LDA $103,X INX | TAY INC $100,X | BNE GST2 LDA $100,X | INC $104,X INX | GST2 LDA $104,X TAY | STA GST3+2 BNE GST2 | GST3 LDA $FF00,Y INC $100,X | LDY YSAVE GST2 LDA $100,X | LDX XSAVE STA GST3+2 | RTS GST3 LDA $FF00,Y | LDY YSAVE | LDX XSAVE | RTS | (39 bajtów) | (35 bajtów) Dzięki tej procedurze można otrzymać duży zysk pamięci, jeśli z parametrów za JSR korzysta więcej podprogramów. Zachowywanie rejestrów indeksujących można pominąć jeżeli ich zawartość nie jest potrzebna. Użycie powyższego podprogramu będzie wyglądać następująco: JSR PUT DTA 155 ... ... PUT JSR GETSTK * Parametr zwracany w akumulatorze... ... RTS Dla przykładu procedura PRINT będzie wyglądać Teraz tak: PRT2 JSR PUTCHAR PRINT JSR GETSTK TAY BNE PRT2 RTS
UNIWERSALNA PROCEDURA KOPIUJĄCA
UC1 STA VAR-252,Y DTA B($2C) UCOPY LDY #251 JSR GETSTK INY BNE UC1 TAY UC2 DEY LDA (VAR),Y STA (VAR+2),Y TYA BNE UC2 RTS Na stronie zerowej należy umieścić czterobajtową zmienną VAR. A oto jak stosować powyższą procedurę: LDX #LEN | JSR UCOPY LOOP LDA SRC-1,X | DTA A(SRC,DST) STA DST-1,X | DTA B(LEN) DEX | BNE LOOP | (11 bajtów) | (8 bajtów) Aby osiągnąć zysk należy użyć wielokrotnie procedury UCOPY (i GETSTK). Myślę, że nie powinno być kłopotów ze znalezieniem zastosowania dla tych procedur w dużych programach. Podprogramem UCOPY można nawet zastąpić kopiowanie pojedynczego słowa: LDA SRC | JSR UCOPY STA DST | DTA A(SRC,DST) LDA SRC+1 | DTA B(2) STA DST+1 | (12 bajtów) | (8 bajtów) Uwaga: w przypadku, gdy ilość wywołań UCOPY jest zbyt mała, aby uzyskać jakikolwiek zysk, można nadal zaoszczędzić jeden bajt - porównajcie lewe kolumny ostatniego i przedostatniego przykładu... UCOPY może być użyta do ustawiania wartości stałych: LDA #1 | JSR UCOPY STA DST | DTA A(LABEL,DST) LDA #15 | DTA 2 STA DST+1 | ... | LABEL DTA 1,15 (10 bytes) | (10 bytes)
Cóż... Zgadza się, nie ma żadnego zysku. Lecz, gdy potrzebujesz te same zmienne wielokrotnie...
należy mieć pewność, że w trakcie wykonywania tego rozkazu nie wystąpi przerwanie sprzętowe, z powodu złego adresu odkładanego na stos rozkaz ten traktować należy jako dwubajtowy. |
madteam.atari8.info © MadTeam, hosted: www.atari8.info |