— Basé sur l'article publié dans Quasar CPC numéro 6, Initiation à l'Assembleur, par Zik.
Comme le laisse présager le titre ci-dessus, nous sommes dans cette rubrique d'initiation à l'assembleur pour parler plus en détail des auto-modifications (j'espère que SNN sera content !).
Eh bien c'est très simple : cela consiste à faire évoluer le programme au cours de son exécution. Par “évoluer”, je veux dire que le programme s'écrit dessus afin de s'adapter à une nouvelle situation ; on peut écrire des valeurs qui serviront de données ou qui correspondront carrément à des instructions , puisque nous avons vu dans la rubrique d'initiation au Z80 que chaque commande de l'assembleur correspond à un nombre ou à une séquence de nombres.
Par exemple, &7F
est le codage en mémoire de LD A,(HL)
et &ED
puis &B0
correspond à la seule instruction LDIR
. Le fait que chaque commande soit en fait un ou plusieurs nombres explique par ailleurs que l'on ait besoin d'assembler son programme avant de le lancer.
Nous verrons contrètemement un des intérêts des auto-modifs (j'abrège !) dans le traditionnel programme d'exemple, mais je vais d'abord vous en montrer de différents types pour se faire la main car ce n'est pas toujours évident.
Imaginons qu'à un moment donné d'un programme on ait à changer l'adresse d'un JP
. Au lieu de faire un test fastidieux et gourmand en temps machine (vous allez dire qu'on n'est pas à ça près mais bon, il est toujours bon de gagner quelques micro-secondes), on a la possibilité de faire une sympathique auto-modification (étonnant n'est-ce pas ?).
Admirez donc cette routine :
MODIF JP &4000 ; Voici un JumP bête mais pas méchant ... LD HL,&5000 ; HL = &5000 LD (MODIF+1),HL ; On met donc &5000 en MODIF+1
Ces deux lignes d'auto-modif changent le JP &4000
initial en tout aussi bête mais pas plus méchant JP &5000
(si vous ne me croyez pas, vous n'avez qu'à essayer vous même1) ). Mais pourquoi ai-je mis MODIF+1
et pas MODIF
(tout court) ?! Eh bien c'est très simple, si on regarde le fabuleux tableau qui vous a été gracieusement dévoilé dans la rubrique d'initiation au Z80 et qu'on y cherche l'instruction JP addr
(puisque c'est notre cas), on voit écrit à droite &C3,ll,hh
. Cela signifie que l'adresse (llhh
) est stockée 1 octet après l'adresse mémoire où est placée la commande (&C3
signifie JP
, l'endroit du saut mémoire est codé à l'envers juste après : JP &5010
donnera en mémoire &C3
,&10
,&50
et non pas &C3
,&50
,&10
qui serait l'équivalent de JP &1050
). On comprend donc le MODIF+1
(si vous n'avez pas compris, essayez donc avec une aspirine).
L'histoire du MODIF+1
est également valable avec un CP data
: on a en mémoire &FE
puis la valeur de comparaison un octet après. Ne soyez donc pas étonnés de recontrer le fameux +1 dans le programme d'exemple. À noter que pour certaines instructions, notamment celles utilisant IX ou IY, c'est un +2 qu'il vous faudra puisque le code d'instruction fait alors 2 octets au lieu d'un seul.
Laissons tomber les auto-modiifcations un instant et penchons-nous sur les vecteurs du système que j'ai utilisé dans mes routines :
&BC0E
: vous devez déjà le connaître, c'est l'équivalent de l'instruction MODE
du BASIC. Il suffit de mettre le numéro du mode désiré dans l'accumulateur puis de faire le CALL
. À la sortie du vecteur, les registres AF, BC, DE et HL ont été modifiés.&BD19
: cela sert à attendre que le canon à électron de votre écran soit prêt à afficher une nouvelle image. Grâce à ce vecteur, on évite les clignotements dans les animations (c'est le FRAME
du BASIC, disponible uniquement à partir du BASIC 1.1 des CPC664 et CPC6128).&BB75
: correspond au LOCATE
du BASIC. H contient l'abscisse et L contient l'ordonnée. AF et HL sont modifiés.&BB5A
: c'est presque le PRINT
du BASIC. Cette routine affiche le caractère dont le code ASCII est dans l'accumulateur. Les registres ne sont pas modifés.&BB1B
: teste si une touche du clavier est enfoncée ; si c'est le cas, A contient le code ASCII du caractère correspondant à la touche et la carry vaut 1. Si aucune touche n'est enfoncée, A n'est pas modifié et la carry vaut 0. Comme A n'est pas modifé, je mets un XOR A
(mise à 0 de A) avant le CALL &BB1B
comme ça je suis sûr que si A=32 (espace) après le CALL
c'est bien parce que la touche espace est enfoncée.
Je pense que le programme comporte assez de commentaires pour que vous compreniez. Il fait rebondir un “O” sur les bords de l'écran ; ainsi il faut un test de maximum et minimum sur les coordonnées en X et en Y. Quand ces limites sont dépassées, on change le sens d'évolution du compteurs (INC
ou 'DEC'') et ce qui va avec. Puis on efface et réaffiche la balle.
Télécharger le listing au format Maxam 1.5
Org &4000 ; ld a,2 call &bc0e ; Équivalent du MODE 2 du Basic (A contient le mode) ; Debut call &bd19 ld hl,(oldpos) ; HL prend les valeurs du 2nd compteur call &bb75 ; = LOCATE H,L (en Basic) ld a," " call &bb5a ; Affichage d'un espace (et donc effacement du O ancien) ; ld hl,(posit) ; HL prend les valeurs du 1er compteur ld (oldpos),hl call &bb75 ; LOCATE H,L ld a,"O" call &bb5a ; Affichage d'un banal "O" ; Mise à jour du compteur en Y ld a,(posit) ; A contient la position du curseur en Y Mod1 inc a Mod2 cp 26 Mod3 call z,decy ld (posit),a ; Mise à jour du compteur en X ld a,(posit+1) ; A contient la position du curseur en X Mod4 inc a Mod5 cp 81 ; (il y a 80 colonnes en mode 2) Mod6 call z,decx ld (posit+1),a ; Test clavier xor a call &bb1b cp 32 jp nz,debut ret ; ; Compteurs ; Posit dw &0101 OldPos dw &0101 ; ; Sous-programmes d'automodification ; ; En ordonnée DecY ld a,&3d ; &3d=DEC A (Voir Quasar CPC 5) ld (mod1),a ; On "poke" DEC A à la place du INC A xor a ; A contient 0 ld (mod2+1),a ; On place 0 à la place du 26 pour avoir CP 0 ld hl,incy ; HL contient l'adresse de la routine appelée IncY ld (mod3+1),hl ; On met cette adresse après le CALL Z ld a,24 ret IncY ld a,&3c ; &3C=INC A (Voir Quasar CPC 5) ld (mod1),a ; On met un INC A à la place du DEC A ld a,26 ; A contient 26 ld (mod2+1),a ; On place 26 à la place du 0 pour avoir CP 26 ld hl,decy ; HL contient l'adresse de la routine appelée DecY ld (mod3+1),hl ; On met cette adresse après le CALL Z ld a,2 ret ; En abscisse DecX ld a,&3d ; &3d=DEC A (Voir Quasar CPC 5) ld (mod4),a ; On "poke" DEC A à la place du INC A xor a ; A contient 0 ld (mod5+1),a ; On place 0 à la place du 01 pour avoir CP 0 ld hl,incx ; HL contient l'adresse de la routine appelée IncX ld (mod6+1),hl ; On met cette adresse après le CALL Z ld a,79 ret IncX ld a,&3c ; &3c=INC A (Voir Quasar CPC 5) ld (mod4),a ; On met un INC A à la place du DEC A ld a,81 ; A contient 81 ld (mod5+1),a ; On place 81 à la place du 0 pour avoir CP 81 ld hl,decx ; HL contient l'adresse de la routine appelée DecX ld (mod6+1),hl ; On met cette adresse après le CALL Z ld a,2 ret