Migration de RASM vers SJASM+

SJASM+ est un assembleur qui est sans conteste l'assembleur z80 de référence pour de nombreuses architectures autres que le CPC (ZX, MSX pour ne citer que les plus courants).
SJASM+ est en effet un assembleur moderne, versatile, rapide et très modulaire, qui respecte de nombreux standards, et dont le développement est toujours très actif. Il suffit de jeter un œil à la page github pour s'en convaincre.
Il possède toutes les fonctionnalités d'un assembleur moderne (macros, labels avancés, modules), il peut même intégrer des scripts en LUA, et son API est accessible en LUA. Sa compilation est simple avec Makefile et Cmake, et il est possible de choisir les modules que l'on veut intégrer. Si on ne veut pas de l’interpréteur LUA, il suffit de passer une variable d'environnement au script de compilation.
Certains éléments superflus n'y sont pas intégrés, comme la conversion de formats audio ou la gestion de sections compressées. Ce genre de fonctions sont plus naturellement externalisées, car peu pratiques à utiliser (la gestion de la taille des sections est infernale dans une approche à 1 passe). Elles n'ont pas vraiment leur place dans un assembleur: il vaut mieux un outil qui fasse moins de choses et les fasses bien, plutôt que son contraire!
Les tests sont massif et systématique à chaque release, ce qui permet d'éviter les régressions (un écueil qu'a pu connaître RASM à plusieurs reprises). Enfin, il est doté d'une documentation claire et complète, que l'on peut retrouver ici, et que je vous conseille de parcourir!

En bref, toutes ces bonnes raisons m'ont poussé à migrer vers cet assembleur, et je vous propose ici un guide rapide pour faciliter cette transition. Bien sûr on pourra utiliser ce même guide pour une migration dans l'autre sens, si l'on souhaite aller vers RASM pour ses fonctions spécifiques. Par exemple pour le support des CPC+, et des cartouches, en attendant leur arrivée sur SJASM+. Le projet est ouvert aux contributions!
Une syntaxe plus stricte
La première chose que l'on va constater c'est que la syntaxe est un peu plus stricte. Les labels commencent obligatoirement sur le 1er caractère de chaque ligne, tandis que les instructions et directives ne peuvent pas commencer sur la première colonne. Il faut les faire précéder d'au moins un espace.
C'est un peu contraignant quand on a pris l'habitude d'écrire du code de façon libre, mais cela force à formater un peu son code (comme en python par exemple) , ce qui au final aide à le rendre plus lisible.
Ensuite, la casse est respectée. Dans RASM, en interne, tout est convertit en majuscule. Avec Sjasm, il faudra prendre soin de respecter la casse dans les noms des labels. Ainsi le label Loop
et le label loop
sont considérés comme deux labels bien distincts.
Il faudra aussi veiller à ne pas combiner majuscules et minuscules dans un opcode. On pourra écrire indifféremment OUT (c),c
ou out (c),c
, mais pas oUt (c),c
par exemple.
Quelques contraintes donc, mais rien de problématique, et dans tous les cas, les messages d'erreur sont très explicites, ils aident à rapidement repérer les problèmes.
Les macros, boucles et modules
La syntaxe des macros et boucles est très similaire, avec quelques simplifications. Pour les macros, les labels sont locaux par défaut: il n'est pas nécessaire de les préfixer par @. De plus, les paramètres n'ont pas besoin d'être entourés par {} pour être utilisés.
MACRO fill pattern,length
ld bc,length
loop:
ld (hl),pattern
dec bc
ld a,b
or c
jr nz, loop
ENDM
Si il n'y a pas de paramètres à passer à la macro, il n'est pas utile de passer (void)
comme cela se faisait avec RASM.
Pour les boucles, même principe, et surtout une hérésie que l'on ne retrouve plus ici, les compteurs commencent par 0!
REPT 8,index
ld hl,#C000+#800*index
ld (hl),1<<index
ENDR
Il faut noter que si vous utilisez beaucoup les boucles, par exemple pour générer beaucoup de données (remplir un écran vidéo avec un motif par exemple), SJASM+ est moins performant que RASM, en terme de vitesse d’exécution. En effet comme SJASM+ procède en plusieurs passes, il déroule intégralement le code avant de l'assembler, ce qui peut générer beaucoup de lignes de code. Même les commentaires semblent dupliqués, ce qui alourdit d'autant le processus d'assemblage. A manier avec précaution donc, sous peine de ralentissements, dans certains cas extrêmes. Mais heureusement, il s'avere que l'interpréteur LUA intégré est tres rapide, donc au besoin, il suffira de remplacer des blocs REPEAT par des blocs LUA, avec des boucles FOR. Nous reviendrons la dessus ultérieurement.
Les modules ont une syntaxe similaire, et n'ont pas de contrainte particulières, comme le fait de se référencer ou d’être imbriqués:
MODULE mymodule
lab1: ; mymodule.lab1
ld hl,@lab1 ; global lab1
ld hl,@lab2 ; global lab2
ld hl,lab2 ; mymodule.lab2
lab2: ; mymodule.lab2
ld hl,lab1 ; mymodule.lab1
ld hl,another_module.lab1 ; another_module.lab1
ld hl,nested.lab1 ; mymodule.nested.lab1
MODULE nested
lab1: ret ; mymodule.nested.lab1
ENDMODULE
ENDMODULE
MODULE another_module
lab1: ret ; another_module.lab1
@lab2: ret ; global label lab2
ENDMODULE
lab1: ret ; global label lab1
A noter l'usage de @ qui permet de définir ou référencer des labels 'globaux'.
Les exports
Cette partie va concerner des aspects spécifiques à l'architecture matérielle pour la quelle on va vouloir produire du code. En particulier pour exporter dans des formats propres aux émulateurs, comme les snapshots (SNA), tapes (TAP) et disks (DSK).
Une souplesse de SJASM+ sur ce point, c'est qu'il est possible d'exporter des fichiers à différents endroits du code source, voire meme de changer d'architecture au cours de l'assemblage.
Il faut commencer par définir l'architecture avec l'instruction DEVICE
, et ensuite d'utiliser une fonction d'export:
DEVICE ZXSPECTRUM128
StartProg:
...
SAVESNA "snapshotname.sna", StartProg
Pour les Amstrad CPCs, il existe les architectures AMSTRADCPC464
et AMSTRADCPC6128
:
DEVICE AMSTRADCPC464
ORG #1000
StartProg:
...
SAVECPCSNA "snapshotname.sna", StartProg
Il n'y a pas de directive RUN
, le point d'entrée est passé directement à la directive SAVESNA
Les expressions
Les expressions arithmétiques sont similaires à RASM, mais certaines opérations arithmétiques, comme les exponentielles, les fonctions sinus et cosinus ne sont pas disponibles. En effet, le core gère du calcul 32 bits en entiers, à la différence de RASM qui effectue tout en flottant (ce qui nécessite d'être vigilant dans certains cas).
Pour palier à cela, SJASM+ intègre un bridge vers LUA, ce qui fait plus que compenser ces manques, car il permet aussi d'étendre le langage, et ce, sans pour autant alourdir inutilement le core de l'assembleur. On pourra alors bénéficier d'un véritable langage, pour générer du code, des patterns (par exemple générer des sprites en mode 1, décalés d'un pixel horizontalement) , des data, ou même rajouter des fonctions d'export (par exemple pour produire des snapshots pour un émulateur), etc.
Ce sujet sera traité dans un autre article, car il dépasse le sujet de la migration vers SJASM+. Mais c'est important de souligner l'existence d'une telle intégration, puisque elle permet d'avoir accès à toute la souplesse d'un véritable langage de script, avec des variables, des expressions arithmétiques poussées et bien plus encore.
Les aides au développement
Fonction simple mais fort pratique, il est possible d'envoyer du texte vers la console, avec l'instruction DISPLAY
. (Si on utilise LUA, on a aussi accès à une instructionprint
).
Des variables accessibles depuis le langages ont définies, en particulier la date (et l'heure) d'assemblage, ce qui peut aider à s'y retrouver dans ses releases!
Enfin, coté intégration dans des IDEs, comme VS codium, la plupart des plugins sont compatibles avec la syntaxe de SJASM, comme Z80-Macroasm ou ASM Code Lens: