Vcera jsem nemohl spat a tak zase programoval. Tentokrat zkousel co vyplodi nejaky C prekladac pro pangram. Zase se pral s tim, aby me to vubec neco zkompilovalo. Nakonec jsem uspel a podival se na kod.
Chapal jsem vse, ale fakt se divil co jsem videl.
Kód:
int pangram(const char * str)
{
str--;
unsigned char c;
unsigned long int alphabet = 0;
while (c = *++str) {
c |= 32; // uppercase
c -= 'a';
if ( c<26 ) alphabet |= (long unsigned int) 1 << c;
}
return alphabet==0x3FFFFFF;
}
Tuhle cast vam popisi.
Kód:
0000 49 _pangram::
0000 DD E5 [15] 50 push ix
0002 DD 21 00 00 [14] 51 ld ix,#0
0006 DD 39 [15] 52 add ix,sp
Cecko bude vytvaret na zasobniku prostor pro lokalni promenne a protoze toho bude hodne,
tak si ulozi puvodni hodnotu zasobniku do IX, ktery si prvne ulozi, aby je pak oba rychleji vratil na puvodni hodnotu.
Kód:
0008 21 F4 FF [10] 53 ld hl, #-12
000B 39 [11] 54 add hl, sp
000C F9 [ 6] 55 ld sp, hl
Tady posune zasobnik o 6 mist dolu = si udela prostor proo sest 16bitovych cisel.
Kód:
56 ;Pangram.c:5: str--;
000D DD 6E 04 [19] 57 ld l, 4 (ix)
0010 DD 66 05 [19] 58 ld h, 5 (ix)
0013 2B [ 6] 59 dec hl
0014 DD 75 04 [19] 60 ld 4 (ix), l
0017 DD 74 05 [19] 61 ld 5 (ix), h
IX ma hodnotu puvodniho zasobniku. Takze:
IX+0..IX+1 - puvodni hodnota IX
IX+2..IX+3 - ret funkce
IX+4..IX+5 - predavany parametr str
Vubec ho nezajima ze bude str znovu brzo potrebovat. Nevidi to... nenecha to v HL.
Kód:
62 ;Pangram.c:7: unsigned long int alphabet = 0;
001A AF [ 4] 63 xor a, a
001B DD 77 FC [19] 64 ld -4 (ix), a
001E DD 77 FD [19] 65 ld -3 (ix), a
0021 DD 77 FE [19] 66 ld -2 (ix), a
0024 DD 77 FF [19] 67 ld -1 (ix), a
Do vytvoreneho prostoru pro lokalni promenne na zasobniku da 32bitove cislo alphabet nastavene na nulu;
Kód:
68 ;Pangram.c:9: while (c = *++str) {
0027 DD 7E 04 [19] 69 ld a, 4 (ix)
002A DD 77 F9 [19] 70 ld -7 (ix), a
002D DD 7E 05 [19] 71 ld a, 5 (ix)
0030 DD 77 FA [19] 72 ld -6 (ix), a
Udela si uplne zbytecne kopii str a zase ji nedrzi v registru... Vypada to, ze jen pro jistotu. Uplne postradam smysl.
I bez analyzy kodu je jasne, ze puvodni str uz nebude potrebovat. Nejde to udelat v programu tak, ze najednou ziskam nejak puvodni hodnotu.
Zadnym IF ELSE, ani rekurzi, nijak.
A udela si diru/misto v lokalnich promenych na pocitadlo smycky.
Nasleduje hlavni smycka while, label 00103$.
Kód:
0033 73 00103$:
0033 DD 34 F9 [23] 74 inc -7 (ix)
0036 20 03 [12] 75 jr NZ,00124$
0038 DD 34 FA [23] 76 inc -6 (ix)
003B 77 00124$:
++str, ale slozite v pameti s pomoci pomocneho navesti a jeste nad kopii... Podivejte se na ty takty. 23 jen za prvni instrukci. Drzet to v HL tak to mohl mit za jeden bajt misto 8 a za 6 taktu!
Kód:
003B DD 6E F9 [19] 78 ld l, -7 (ix)
003E DD 66 FA [19] 79 ld h, -6 (ix)
0041 7E [ 7] 80 ld a, (hl)
0042 DD 77 FB [19] 81 ld -5 (ix), a
0045 DD 77 F8 [19] 82 ld -8 (ix), a
0048 DD 7E FB [19] 83 ld a, -5 (ix)
004B B7 [ 4] 84 or a, a
004C 28 5D [12] 85 jr Z,00105$
Tady to s te inkrementovane kopie v pameti musi do HL dat, aby mohl nacist z toho ukazatele znak.
A ted pozor, ten znak si ulozi rovnou 2x. Jednou do te mezery co ma v lokalnich datech a podruhe jeste na -8.
Vypada to, ze tu -5 pouzije jen proto, aby mohl obnovit akumulator, pote co ho pouzije na ulozeni do -8, takze ho nezmeni. Nikde jinde se nepouziva. Uzasne.
A to dokonce predtim nez zjisti zda ho bude jeste potrebovat, protoze je to pred skokem a mimo smycku se s tim nic nedela.
On za skok nevidi.
Vsechno instrukce s ix jsou tady nadbytecne. Jen 3 bajty kodu jsou potreba.
Kód:
86 ;Pangram.c:10: c |= 32; // uppercase
004E DD 4E F8 [19] 87 ld c, -8 (ix)
0051 CB E9 [ 8] 88 set 5, c
0053 79 [ 4] 89 ld a, c
On to ma stale v akumulatoru a presto to bude tahat z te -8 a jeste do registru c, aby to musel nastavovat pres set! A pak to strci do akumulatoru.
Naprosto neuveritelne. Jako slepec. Tam musi chybet uplne predavani dat z predchoziho kodu.
Tohle slo napsat na 2 bajty a 7 taktu prostym "or 32".
Kód:
90 ;Pangram.c:11: c -= 'a';
0054 C6 9F [ 7] 91 add a, #0x9f
Nevim proc voli pricteni zaporne hodnoty, nez odecet te hodnoty, ale nema to zadnou rezii navic.
Kód:
92 ;Pangram.c:12: if ( c<26 ) alphabet |= (long unsigned int) 1 << c;
0056 DD 77 F8 [19] 93 ld -8 (ix), a
0059 D6 1A [ 7] 94 sub a, #0x1a
005B 30 D6 [12] 95 jr NC,00103$
Tohle je ale strasne chytre. To bych chtel mit taky.
On tady konecne vidi dopredu a vi, ze tohle je posledni radek smycky a skoci rovno zpet a ne prvne za konec radku a pak teprve zpet.
Vsimnete si jak prvne ulozi akumulator do pameti -8, aby ho za chvili mohl z pameti nacist do registru b.
Rikate si proc? Tak na tohle neznam odpoved. Proste nema data. Mohl pouzit i CP misto SUB.
Vic ix, vic bajtu a taktu.
Kód:
005D DD 46 F8 [19] 96 ld b, -8 (ix)
0060 DD 36 F4 01 [19] 97 ld -12 (ix), #0x01
0064 DD 36 F5 00 [19] 98 ld -11 (ix), #0x00
0068 DD 36 F6 00 [19] 99 ld -10 (ix), #0x00
006C DD 36 F7 00 [19] 100 ld -9 (ix), #0x00
0070 04 [ 4] 101 inc b
0071 18 10 [12] 102 jr 00126$
Inicializace smycky pro opakovany bitovy posun vlevo.
On to bude ted dost intenzivne pouzivat a nic jineho, ale do registru to neda ani nahodou.
Pritom takove add HL,HL pro nizsich 8 bajtu...
Kód:
0073 103 00125$:
0073 DD CB F4 26 [23] 104 sla -12 (ix)
0077 DD CB F5 16 [23] 105 rl -11 (ix)
007B DD CB F6 16 [23] 106 rl -10 (ix)
007F DD CB F7 16 [23] 107 rl -9 (ix)
0083 108 00126$:
0083 10 EE [13] 109 djnz 00125$
...by mu usetrilo 6 bajtu a bylo vic jak 4x rychleji.
Je to takova zajimava ukazka, ze nam staci pro programovani jen jeden ix registr a dostatek pameti, i pro ten nabobtnaly kod.
Tohle uplne krici o to ze mam volne idealni pro rotaci HL,A, a jeden z registru D,E,C. Nezajem, nebo spis slepota.
Kód:
0085 DD 7E FC [19] 110 ld a, -4 (ix)
0088 DD B6 F4 [19] 111 or a, -12 (ix)
008B DD 77 FC [19] 112 ld -4 (ix), a
008E DD 7E FD [19] 113 ld a, -3 (ix)
0091 DD B6 F5 [19] 114 or a, -11 (ix)
0094 DD 77 FD [19] 115 ld -3 (ix), a
0097 DD 7E FE [19] 116 ld a, -2 (ix)
009A DD B6 F6 [19] 117 or a, -10 (ix)
009D DD 77 FE [19] 118 ld -2 (ix), a
00A0 DD 7E FF [19] 119 ld a, -1 (ix)
00A3 DD B6 F7 [19] 120 or a, -9 (ix)
00A6 DD 77 FF [19] 121 ld -1 (ix), a
00A9 18 88 [12] 122 jr 00103$
32 bitovy OR dvou cisel v pameti.
Proc nema to jedno cislo v registech nepochopime. Fakt bych chtel umet videt jak to delaji. Ja mam izolovana slova nebo spojeni slov.
To je v podstate taky jedno slovo a uvnitr mohu delat optimalizaci, ne z dat v okoli, protoze pouzivam pouha makra.
Teoreticky bych mohl mit nejake udaje z prechozich slov, ale to by bylo celkem slozite bezpecne implementovat.
Mohu si udelat makra __HL, __DE, __BC a drzet v nich co obsahuji. Zda je to znama hodnota, protoze jsem tam predtim vlozil konstantu, nebo uz ne.
Ale to by si vyzadalo osetreni vsech skoku. Na nic nezapomenout.
A mam automaticky prvnich 32 bitu vzdy v registrech, takze se mi nemuze stat z principu delat operaci nad dvema operandy v pameti.
To bych musel mit slovo jako PUSH2_OR() a programator to tak chtel mit a hodit me 2 pointery.
Kód:
00AB 123 00105$:
124 ;Pangram.c:15: return alphabet==0x3FFFFFF;
00AB DD 7E FC [19] 125 ld a, -4 (ix)
00AE 3C [ 4] 126 inc a
00AF 20 16 [12] 127 jr NZ,00127$
00B1 DD 7E FD [19] 128 ld a, -3 (ix)
00B4 3C [ 4] 129 inc a
00B5 20 10 [12] 130 jr NZ,00127$
00B7 DD 7E FE [19] 131 ld a, -2 (ix)
00BA 3C [ 4] 132 inc a
00BB 20 0A [12] 133 jr NZ,00127$
00BD DD 7E FF [19] 134 ld a, -1 (ix)
00C0 D6 03 [ 7] 135 sub a, #0x03
Zvolil metodu, ktera muze byt u smycky idealni, kdy prvne resi nejnizsi bity.
Bohuzel tady se to nevyplati, protoze to neni podminka smycky.
Tomu se ale neda nic vytknout, to uz chce vic informaci a vetsi nadhled, ze tahle cast kodu probehne jen jednou.
Kód:
00C2 20 03 [12] 136 jr NZ, 00127$
00C4 3E 01 [ 7] 137 ld a, #0x01
00C6 20 138 .db #0x20
00C7 139 00127$:
00C7 AF [ 4] 140 xor a, a
00C8 141 00128$:
00C8 6F [ 4] 142 ld l, a
00C9 26 00 [ 7] 143 ld h, #0x00
V tehle casti je jedna genialni vec.
Ten bajt 0x20 je zacatek instrukce jr NZ. On vi ze protoze to selhalo predtim a nic se nezmenil tak to selze znovu a odmaze to xor a,a.
Tady musim podotknout, ze on vyzaduje oba registry v xor! Pasmo naopak tohle oznaci jako chybu a selze...
Proc? Co je snandart? Ze by byl problem v tom, ze tohle neni jen cilene na Z80? Ale i jine procesory?
Zrovna u "xor a,a", me nejaky novejsi klon Z80 asi novou instrukci nezmate. "sub c" by mohlo, kdyby mu pridali instrukce pro odcitani nejen u akulutaru.
Zpet k tomu jr NZ. Ja bych automaticky pouzil variantu s ld b,* nebo ld bc,** a zabil tak hodnotu v jednom nebo vice registru. Tohle je chytrejsi a pritom tak proste.
Dokonce by to bylo o bajt mensi a obe varianty pokazde o jeden takt rychlejsi.
Kód:
jr NZ,00127$
db 0x12 0x01 ; 12 01 .. = ld hl, 0x2e01 ld hl + nop + ld h = 10+4+7 = 21
00127$:
ld l, 0x00 ; 2e 00 ld l + ld h = 7+7 = 14
ld h, 0x00 ; 26 00
Cele je to ale uplne zbytecne, protoze za chvili je konec a nepouzivat zbytecne ix mohl to napsat
Kód:
00127$:
ld hl, 0x0000 ; 3:10 Replace str with a return value of 0 or 1
ret NZ ; 1:5/11
inc l ; 1:4
Kód:
144 ;Pangram.c:16: }
00CB DD F9 [10] 145 ld sp, ix
00CD DD E1 [14] 146 pop ix
00CF C9 [10] 147 ret
Vrati zasobnik zpet a hodnotu ix.