Migration de RASM vers SJASM+

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.  

GitHub - z00m128/sjasmplus: Command-line cross-compiler of assembly language for Z80 CPU.
Command-line cross-compiler of assembly language for Z80 CPU. - GitHub - z00m128/sjasmplus: Command-line cross-compiler of assembly language for Z80 CPU.

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!

SjASMPlus 1.20.2 Documentation [2023-02-14]

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:

GitHub - mborik/z80-macroasm-vscode: Support for Z80 macro-assemblers in Visual Studio Code
Support for Z80 macro-assemblers in Visual Studio Code - GitHub - mborik/z80-macroasm-vscode: Support for Z80 macro-assemblers in Visual Studio Code
GitHub - maziac/asm-code-lens: A vscode language server that enables code lens, references, hover information and symbol renaming in asssembler files.
A vscode language server that enables code lens, references, hover information and symbol renaming in asssembler files. - GitHub - maziac/asm-code-lens: A vscode language server that enables code l...
GitHub - theNestruo/z80-asm-meter-vscode: Z80 Assembly meter extension for Visual Studio Code
Z80 Assembly meter extension for Visual Studio Code - GitHub - theNestruo/z80-asm-meter-vscode: Z80 Assembly meter extension for Visual Studio Code