ASM680X0 - Chapitre 3
Un article de GuruMed.
Je fais une sous routine
Nous avons déjà essayé de créer des programmes court de quelques instructions au chapitre1 pour nous familiariser avec la syntaxe. Maintenant nous allons apprendre tout ce qui sera necessaire à l'élaboration d'un programme plus long et plus utile, toujours écrit à 100% en assembleur. Il est d'abord necessaire d'apprendre en détail l'architecture du fonctionnement d'un exécutable, pour comprendre exactement ce qui se passe au niveau le plus bas. Nous verrons seulement plus tard les opérations algorithmiques courantes tels que les tests, les boucles, etc...
Familiarisons nous avec un registre spécial que possèdent tout les CPU, dont on n'entendra pas parler souvent: le PC, Program Counter. Théoriquement, même le programmeur assembleur n'a pas à y toucher. Il représente simplement un pointeur sur l'emplacement mémoire actuellement exécuté. Quand un programme s'exécute, le PC exécute l'instruction qu'il pointe, puis s'incrémente pour passer à la suivante. Les zones mémoires potentiellement pointés par le PC sont donc celles ou le code est chargé dans le CPU pour être exécuté (et dans la cache instruction depuis le 68020).
Comment réaliser un saut en sous-routine depuis une routine ? 2 instructions sont disponibles: bsr (branchement vers sous-routines) et jsr (jump, saut vers sous-routine. )
bsr(.b,.w,.l) labeldebutroutine
jsr labeldebutroutine ; vers autres section mémoire.
jsr decalage(ax) ; vers un code pointé par ax+decalage
Dans leurs effets, ces instructions sont identiques. (voir plus loin.) mais elles différent dans leurs méthodes d’accès à une autre zone mémoire de code:
Bsr ...ne peut être utilisé qu'avec un label écrit dans le code, DANS LA MEME SECTION DE CODE.(voir ce qu'est une section de code au chapitre1). le format spécifié (.b,.s ou .w ou .l) défini la taille de la donnée (8, 16 ou 32bit) maximum pour exprimer le décalage mémoire du PC jusqu'à ce label. par exemple, ' bsr.b label' ne peut être compilé que si 'label' est déclaré entre [-128 et 127] octets de l'instruction bsr elle-même dans la mémoire. Cela ne correspond qu'à une vingtaine de lignes de code, avant ou après. Si l'écart entre 'label' et bsr est trop grand, le compilateur indiquera une erreur et ne compilera pas. même chose pour .w qui permet un écart 256 fois plus grand.
Parenthèse: Le format .b .s .w ou .l spécifié pour bsr, jsr mais aussi les instructions de branchement comme 'bra' est rarement une source de bug, il suffit de corriger si l'assembleur détecte un format trop court: .l marche toujours, mais .b et .w sont censé être plus rapide. Certaines options d'optimisations existent aussi pour gérer cela automatiquement dans les assembleurs (devpac, phxass, asmone).
Jsr ...peut s'utiliser dans tout les cas ou bsr est valide, mais est toujours en .l (donc pas besoin de le spécifier) Dans le cas ou un saut en sous-routine appelle une autre section code, jsr est obligatoire.
Jsr permet aussi de sauter en sous-routine dans un espace mémoire pointé par un registre d'adressage: ces syntaxes possible avec jsr sont très puissantes:
Un registre d'adressage est manipulable à loisir, alors qu'un simple label (dans le cas de bsr) est complétement statique et compilé une bonne fois pour toute. Donc, jsr permet de rendre dynamique un saut en sous-routine: le même jsr pourra faire sauter vers une routine ou une autre !
jsr (a6)
jsr fonction_label(a6)
Dans les exemples au dessus, un saut en sous routine va se faire: 'vers l'endroit pointé par a6' Puis vers 'a6 plus fonction_label' ou fonction_label est une constante définie précédemment, pour avoir un code plus lisible. utiliser jsr -64(a6) est donc une syntaxe équivalente, avec un saut en sous-routine vers 'la valeur de a6-64'.
Important: Nous verrons que le système de bibliothèque partagé de l'amigaOS, les '.library' est en fait basé sur des appels de fonctions qui utilisent jsr.
OK, J'ai hâte de faire vraiment une sous-routine.
Expliquons maintenant comment faire une sous routine, et ce qu'il se passe exactement lors du saut, puis du retour 'après le bsr ou jsr appelant':
Pour construire une sous-routine simple, seul un label et une instruction 'rts' (return to the source.) sont nécessaire. une première sous-routine simple peut ressembler à:
ma_sous_routine_simple: ; un label identifiable pour savoir ou ça commence
;(mon code ici)
rts ; retour au code d'appel.
Et un appel à cette sous routine ressemblera à:
; code juste avant le saut en sous-routine
bsr.w ma_sous_routine_simple
; suite du code après retour
On peut comprendre que la sous-routine (on dira fonction) 'ma_sous_routine_simple' peut être appelé depuis n'importe ou, par plusieurs autres fonctions. Comment l'instruction 'rts' sait-elle revenir d'ou elle vient ?
Que se passe-t-il lors d'un saut en sous-routine ?
Pour bien comprendre, il faut d'abord connaître le fonctionnement de la 'pile de la tâche':
Finalement, on va pas faire de sous routine tout de suite, on va d'abord apprendre ce qu'est la pile de tâche.
Quand il lance un programme, l'AmigaOS place dans le registre a7, (on peut aussi utiliser 'sp' pour stack pointer, pointeur de pile, c'est le même), l'adresse d'une zone mémoire dédié à la tâche, la 'pile de la tâche'. Vous pouvez remarquer, sous le workbench, dans le menu icône/information, qu'une taille maximum de pile est spécifié pour chaque exécutable. ( environ 4 ou 8 kilo-octets à défaut je crois, cela dépend.) Cette zone mémoire sert à 2 choses dans la vie d'une tâche:
- mémoriser les endroits d’où partent les appels en sous-routines pour y revenir.
- faire de courtes allocations mémoire, rapide à allouer et à libérer, temporaires et volatiles.
En entrée du programme, a7 (sp donc) pointe en fait LA FIN de la pile, car elle est utilisé 'à l'envers.' C'est une pile au sens informatique du terme, c'est à dire une mémoire 'LIFO' (last in, first out: dernier rentré, premier sorti.) en opposition au terme 'FIFO' (premier entrée, premier sorti) qui représente des 'files'. En clair, en informatique, 2 opérations sont possibles avec une pile:
- rentrer une nouvelle information sur le dessus de la pile, sur celles entrées avant.
- sortir la dernière opération de la pile pour la lire.
Ainsi, si on rentre dans l'ordre: 1,2,3,4,5,6 dans une pile, quand on la lira, on aura: 6,5,4,3,2,1. (pour une file FIFO , on lirait 1,2,3,4,5,6.)
Un programme, et plus généralement une fonction, décide de la façon dont il gère la pile: il peut demander d'en allouer temporairement une partie, qu'il devra rendre ensuite. Cela se fait très simplement: a7/sp pointe à tout moment le dernier espace utilisé par la pile, et en décalant le pointer de pile vers l'arrière, l'espace ainsi créée est utilisable par la fonction, mais attention: cet espace est volatil, on dit qu'il est local à la fonction, et doit être rendu en sortie, ce qui se fait en remontant le pointeur de pile à la position ou on l'avait reçu.
On peut donc utiliser la pile de la tache pour y stocker, par exemple, la valeur 32 bit de d4, puis la relire:
move.l d4,-(sp) ; réalise d'abord sp=sp-4 , puis move.l d4,(sp) ; la valeur de d4 est sauvegardé temporairement dans la pile, ; on pourra donc utiliser d4 pour autre chose entre ces 2 instructions. ; On doit utiliser les registres au maximum en assembleur car ; ils sont plus rapide que des appels mémoire. Il est parfois ; utile de délester les registres d'une valeur qui ne sert pas ; immédiatement. On la restaurera avec: move.l (sp)+,d4 ; réalise move.l (sp),d4 , PUIS sp=sp+4
De la même façon, imaginons qu'on ait besoin de d4 et d5 pour 'autre chose', pour une opération par exemple, mais que leurs valeurs doivent être les mêmes avant et après cette opération. stockons-les dans la pile, elle est faite pour ça:
move.l d4,-(sp) ; stockage des valeurs de d4/d5 move.l d5,-(sp) ; ( code utilisant d4 et d5) move.l (sp)+,d5 move.l (sp)+,d4 ; déstockage d4/d5 ( dans l'AUTRE SENS pour retrouver les mêmes valeurs !) ; note: ces suites de "move", sont optimisables par des '''movem''', voir plus loin.
Notez qu'on utilise toujours des expressions -(ax) et (ax)+, car sp pointe le dernier espace mémoire rempli. le premier espace libre est juste avant sp.
Dés lors, Comment fonctionnent jsr/bsr et rts ?
lors d'une utilisation de jsr et bsr, de façon invisible, ceci se produit:
move.l pc,-(sp) ; stockage de l'adresse retour dans la pile. ;(puis seulement aprés, saut.)
et lors d'un RTS, cet opération invisible à lieu:
move.l (sp)+,pc ; l'adresse retour est lue dans la pile et c'est là qu'on retourne !!!
Et voilà, maintenant vous savez qu'un saut dans une fonction ou une sous-routine utilise la pile pour stocker l'adresse de retour, et donc chaque saut en sous routine avec bsr et jsr décale la pile de "-4" ( contrairement aux autres instructions de saut immédiat comme jmp, bra, et aux nombreuses instructions de saut conditionnels: blt, ble, bgt bge, dbf ,... qui elles ne touchent pas à la pile )
ainsi la pile ne cesse de reculer (-(sp)) et d'avancer ((sp)+), pendant la vie d'un programme. mais attention, ceci est une GRANDE SOURCE DE BUG: la pile doit pointer au meme endroit en entrée ET en sortie de sous-routine (ou de tâche.), donc pour un -(sp) doit correspondre un (sp)+. Rendre la pile avec une valeur différente que celle où on l'a reçue fera planter la machine!
Dernière note sur les piles: On peut parfaitement imaginer, en algorithmique, faire du 'code récursif'. Il s'agit d'appeler une fonction 'A' depuis... la même fonction 'A'.
En pratique, cela reviendrais à faire une boucle infinie, qui , si vous suivez, 'exploserais' la pile, puisque les bsr ou jsr s'enchainant sans rts: la pile stockerait de plus en plus d'adresse retour, jusqu'à dépasser sa capacité maximum, ce qui est très mal géré sur les amigaos 68k.
Il est néanmoins parfaitement possible d'écrire des algorithmes récursifs sur amiga, et je vous y invite. En fait en algorithmique, toute fonction récursive doit contenir une 'condition de sortie' comme suit:
label_debut_programme:
; ma_fonction_recursive va s'appeller 15 fois elle-même et revenir:
moveq #15,d0
bsr ma_fonction_recursive
; je suis aussi une sous fonction, donc je dois terminer par:
rts
ma_fonction_recursive:
subq.l #1,d0
tst.l d0 ; teste explicitement si plus petit que 0 (note: voir optimisation des tests)
; '''b'''ranch if more '''l'''ittle or '''e'''qual, saute si plus petit ou égal.
; aussi: ".s" est un saut 8bit, possible car le label 'pluspetitque' est pas loin.
ble.s pluspetitque
; donc si plus grand que 0 on "récurse" encore en sautant vers nous-même !
bsr ma_fonction_recursive
pluspetitque:
; fin de la sous-routine, on retourne.
rts
Ici, label_debut_programme appele 'ma_fonction_recursive' avec 15 dans d0, et ma_fonction_recursive va se lancer elle même 15 fois décalant dans sa course la pile de 15*4 octet, puis quand d0 va atteindre 0, 15 rts vont "refermer" la pile, qui reviendra à son niveau original du début du programme.
...Tout ça pour vous faire comprendre que réaliser des algos récursif est possible, mais l'AmigaOS ne gérant PAS les dépassements de pile, il faut faire attention à ne pas la dépasser: donc maîtriser la quantité mémoire demandé par les fonctions à la pile, la profondeur de récursion d'un programme, et au besoin demander plus de mémoire à défaut pour votre exécutable dans la fenêtre info du Workbench.
Ceci est vrai pour tout programme amiga, même ceux programmé en C !
Programmer des algorithmes récursifs est par exemple utile pour parcourir des bases de données géré comme des arbres, et c'est courant en théorie des graphes. (Autre note pour être complet: tout algorithme récursif peut toutefois être déplié en un autre algorithme itératif, ce qui peut être utile sur amiga pour justement ne pas exploser la pile ... j'avais fait un article là dessus, devenu introuvable... bon à faire)
Passer des paramètres à une sous-routine, récupérer ces paramètres depuis la sous-routine.
Chaque fonctions doit accomplir quelque chose, et le plus souvent on a besoin de spécifier des paramètres en entrée à la fonction pour qu'elle sache exactement quoi faire.
La notion de signature de fonction est très forte en informatique: une signature de fonction définie son nom, quels sont les paramètres d'entrées, combien ils sont, leurs types respectifs, optionnellement le (ou les) paramètres de sorties.
Ça à l'air de devenir compliqué, mais justement voilà un exemple ou les choses deviennent plus simple en assembleur:
Dans le court exemple de fonction que nous avons vu, nous avions besoin de spécifier une valeur d'entrée à la fonction récursive: intuitivement, nous avions placé la valeur 15 dans le registre d0 avant le saut, et naturellement la fonction appelée utilisait ensuite d0 comme paramètre d'entrée. En assembleur 680X0, il peut donc être très facile de définir des paramètres d'entrée et de sortie, en utilisant les registres d'une façon ou d'une autre ! on peut par exemple définir une fonction 'produitScalaire'
; fonction produitScalaire
; calcule le produit scalaire de 2 vecteurs 2d A et B
; paramètres d'entrée:
; d0.w valeur x du vecteur A sur entier 16 bits signés
; d1.w valeur y du vecteur A sur entier 16 bits signés
; d2.w valeur x du vecteur B sur entier 16 bits signés
; d3.w valeur y du vecteur B sur entier 16 bits signés
; paramètre de sortie: d0.l sur 32 bits signés
produitScalaire:
muls.w d2,d0 ; on se souvient des cours précédents, pas de questions sur les .w/.l !!!
muls.w d3,d1
add.l d1,d0
rts
... un cas d'utilisation pourrait être:
; a0 pointerait une liste de vecteurs 2d avec des x et y:
; a1 pointerait une zone mémoire ou écrire le produit scalaire
move.w (a0)+,d0
move.w (a0)+,d1
move.w (a0)+,d2
move.w (a0),d3
bsr produitScalaire
move.l d0,(a1)
Note optionnelle: en assembleur 680X0, pour des raisons d'optimisations, des codes aussi court que 'produitScalaire' ne justifient pas qu'on en fasse des fonctions, car d'abord le code d'appel est ici plus long que le code appelé, et ensuite faire trop de saut bsr à tout bout de champ ralentie l'action (ne serait ce que pour les écritures/lectures en pile qu'on a vu.) On préfère alors ré-écrire la routine en lieu et place de l'appel bsr, cela dit, on perdrait l'avantage de pouvoir modifier le code de tout les endroits ou on fait des produits scalaire "en une fois", au même endroit. La solution pour le codeur assembleur est alors d'utiliser des "macros", qui permettent de faire des fonctions 'inline', c'est à dire des fonctions écrites une fois, mais automatiquement ré-écrite dans le code pour chaque appel, lors de l'assemblage. Les macros existent dans d'autres langages comme le C, mais elles y sont mal vues. Dans le cadre de l'assembleur, c'est un mécanisme indispensable qui apporte souplesse et clartés. (note: en PowerPC et dans les assembleurs 8bit, on ne fait QUE des macros.)
Bon je vais essayer de faire des exemples de fonctions vraiment utile pour la suite :)
Pouvoir utiliser tout les registres dans sa fonction et les rendre dans l'état initial, et aussi: la redoutable instruction movem !
Une fonction appelante peut elle aussi avoir besoin des précieux registres, pour ses calculs et autres, donc l'appelant aimerait pouvoir les retrouver intacts au retour d'une fonction.
Mais d'un autre coté, l'appelé peut avoir lui aussi besoin des mêmes registres. Pas de problème ! la pile est fait pour ça ! Au passage, on va apprendre une des instructions les plus puissantes du 68000: le redoutable movem pour faire plein de move d'un coup, et aussi dbf pour faire des boucles et écrire une fonction un peu étoffée.
TODO: CETTE FONCTION D'EXEMPLE EST PAS FINIE
; fonction: remplir une table 2d avec
; d0:
; a0: pointeur du début de la table à écrire, en .w
; a1: pointeur de la table à lire
remplirTableAvecSuiteFibonacci
movem.l d0-d7/a0-a6,-(sp) ; empilage de 15 registres 32 bits (60 octets copié en une instruction)
; à partir d'ici on peut faire ce qu'on veut avec tout les registres !
; on va faire 2 boucles imbriquées pour remplir une table 2d !
; on va utiliser l'instruction dbf (decrement, branch if false(0)) pour
; revenir de la fin d'une boucle au début d'une boucle (les labels .loop)
; notre utilisation de dbf pour les boucles fait que
; le registre compteur associé doit être décrémenté une fois préalablement !
subq.w #1,d0
subq.w #1,d1
.loopY
; à chaque ligne, il faut réinitialiser le compteur de largeur
move.w d0,d2
.loopX
; on passe ici pour chaque élément de la table 2D
; à cet endroit:
; d0 garde largeur table-1
; d1 compteur hauteur
; d2 compteur largeur
; d3 sert pour le calcul
move.w d0,d3
add.w d2,d3
move.w d3,(a0)+ ; écrit une valeur à l'endroit pointé par a0 et avance sur la table.
dbf d2,.loopX
dbf d1,.loopY
movem.l (sp)+,d0-d7/a0-a6 ; dépilage de 15 registres 32 bits vers les registres.
; à partir d'ici les 15 registres ont retrouvés leurs valeurs initiales.
rts
Importantes choses à savoir impérativement sur movem ! à apprendre par coeur !
- movem.l permet de faire l'équivalent de plusieurs move.l dx,-(ax) et move.l ax,-(ax), mais chacun des 15 registres sont optionnels, et la taille du décalage dépends du nombre de registres effectifs: pour empiler seulement d0 et d7 on peut écrire movem.l d0/d7,-(sp) et la pile sera décalé de 8!
- Si on écrit movem.l d2-d4,-(sp), avec un '-' au lieu de '/', c'est la suite de registres inclus par le tiret qui sera empilé,donc, "d2-d4" est équivalent à "d2/d3/d4".
- Pour être clair: d0-d7/a0-a6 décrit les 15 registres empilables, et est équivalent à d0/d1/d2/d3/d4/d5/d6/d7/a0/a1/a2/a3/a4/a5/a6
- Important: L'ordre d'écriture du movem est toujours l'ordre des registres, les dx d'abord, les ax ensuite! Si on écrit movem.l d7/d0,-(sp) c'est équivalent à movem.l d0/d7,-(sp)
- movem.l (sp)+,(SuiteDeRegistres) permet de restaurer l'état précédemment empilé par movem.l (SuiteDeRegistres),-(sp) , quelle que soit l'expression "SuiteDeRegistres", donc bien entendu, les registres ne sont pas inversés et reviennent dans le même ordre qu'avant.
- Contrairement au "move" classique qui possède plus de modes d'adressages, movem ne possèdent que peu de modes d'adressages hors ceux tapés plus haut, on peut juste faire: movem.l (ax),registres et movem.l registres,(ax) (c'est à dire les mêmes sans les + et -) On ne peut pas mettre les - et + d'incrémentation/décrémentation automatique des pointeurs dans un autre ordre, l'assembler vous fera une erreur si vous essayez.
- ceci est peut connu et utile: on peut utiliser movem en mode .w (16 bit). Dans le cas de la copie mémoire vers registre, une particularité assez géniale se passe alors: des "ext.w" (voir cours précédent, extension du signe de 16bit vers 32 bit) est alors appliqué à tout les registres concernés ! Si vous avez une suite de move.w et ext.w sur plusieurs registres, un seul movem.w peut les remplacer !
- Sur le bon vieux 68000, utiliser des movem autant que possible optimise beaucoup, car moins de code instruction est nécessaire.(à partir du 68020 on a un cache instruction qui permet de ne pas passer la moitié du temps du bus à charger du code)
Et maintenant au tour de l'instruction dbf!
dbf fait partie de la famille d'instruction 68000 'dbcc', décrément et branchement conditionnel ou le 'cc' correspond à un conditionnement logique:
- dbf décrémente le registre de 1, teste si il est faux(zéro) et branche un label selon.
- dbt décrémente le registre de 1, teste si il est true (vrai, différent de zéro) et branche un label selon.
- dbra décrémente le registre de 1, et branche vers un label sans condition.
(...Je découvre qu'il en existe 14 en tout, qui tous commence par un décrément du registre: allez pour la forme les voilà tous: dbcc carry clear, dbcs carry set, dbeq si égal à zéro, dbf,dbt,dbge greater or equal, dbgt greater than, dbhi hight (?), dble less or equal, dbls low or same, dblt less than, dbmi minus (?), dbne not equal, dbpl plus (?), dbvc overflow clear, dbvs overflow set. Si vous n'avez pas tout compris non plus, les equals sont des comparaisons à zéro, et les overflow testent les dépassement", tout ceci est très peu utilisé par ailleurs.)
Les choses importantes à savoir avec dbf pour ne pas faire de plantage, sont:
- dbf, dbeq et consort sont bien utile pour gérer les boucles car ce sont des instructions "courtes", et elles font un décrément puis un branchement en une seule instruction.
- Le registre associé est forcément un des 8 registres de données dx, pas de ax possible.
- Le plus important à noter: beaucoup font l'erreur on peut écrire dbf.s, dbf.w mais, pour tout les dbcc, le format 8/16/32 bits (.s,.b,.w) ne s'applique pas au registre décrémenté et testé, mais à la taille du domaine de branchage, comme pour bra,bsr,jsr,jmp ... de plus le registre décrémenté utilisé par l'instruction est OBLIGATOIREMENT utilisé en 16bit (.w) quelque soit le format spécifié.
Ça signifie tout simplement que le nombre maximum d'itération d'une boucle géré par un dbf est sur 16 bits non signé, donc 2^16, soit 65536. Il faudra utiliser une autre instruction de test pour des boucles nécessitant de pouvoir dépasser ce nombre. (cmp.l, et branchement avec bcc par exemple.)
Passer des paramètres à une fonction, par la pile:
TODO à finir
Comme une fonction appelante peut elle aussi avoir besoin des précieux registres, pour ses calculs et autres, et aimerait pouvoir les retrouver intacts au retour d'une fonction, et comme on peut avoir besoin de passer plus de paramètres que ne peuvent en contenir les pauvres 15 registres disponibles,
on peut tout simplement utiliser notre bonne vieille pile de tâche pour passer les registres:
J'apprends aussi ce qu'est l'ABI du 68000 et à quoi ça sert.
TODO à finir
Techniquement, il est possible d'utiliser les registres, la mémoire qu'on s'est alloué (avec les fonctions exec AllocMem() et FreeMem, il faudra voir ça), et la pile de la tâche comme bon vous semble, tant que vous n'allez pas lire/écrire la mémoire qui n'est pas à vous, ou corrompre le pointeur de pile, tout ira bien.
Mais en informatique, il est nécessaire d'utiliser certains standards pour permettre à tout le monde de mieux travailler, en permettant aux autres de réutiliser son code. l'ABI ( Application Binary Interface ) du 68000 est faite pour ça. Il s'agit de décrire comment sont passé des paramètres en entrée dans une fonction, et comment ces fonctions vont renvoyer des informations en sortie.
Quand il a construit son processeur, monsieur motorola a bien spécifié cela (voir tableau). Que signifie-t-il ? que a7, comme on l'a vu, est toujours le pointeur de pile, donc pas touche, sauf stockage/destockage ponctuel de données. ensuite d0,d1,a0,a1 sont les seuls registres 'volatils', les autres sont 'persistants'. Cela signifie que lorsqu'on saute dans une fonction 'standard', il faut s'attendre à ce que cette fonction 'modifie' les valeurs de d0/d1/a0/a1, alors qu'il est garanti que les autres (de d2 à d7, puis de a2 à a6) garderont leurs valeurs au retour de la fonction.
Aussi, les registres volatils sont fréquemment utilisés pour passer des parametres en 'entrée' aux fonctions, ainsi que pour donner des résultats en sortie, lorsque necessaire.
Nous respecterons cette ABI dans certaines fonctions importantes que nous écrirons. Cela nous permettra, par exemple, de les utiliser depuis d'autres languages comme le C, ou de les mettres dans des .o pour les partager entre plusieurs programmes (voir début chapitre 1) une bonne fois pour toute, avec des documentations indiquant quelles fonctions sont présentes, leurs noms, quels registres correspondent à quelles données en entrée et en sortie, de sorte qu'un individu lambda, avec cette simple documentation et ce simple .o puisse réutiliser votre code asm sans avoir à l'assembler ou se soucier du code à l'intérieur.
quels registres sont volatiles ou pas dans un passage...
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| VOLATILE | VOLATILE |
...de fonction standard ?
| a0 | a1 | a2 | a3 | a4 | a5 | a6 | a7 (sp) |
| VOLATILE | VOLATILE | pile |
Bon, je fais vraiment une sous-routine.
TODO A finir
