Obligement - L'Amiga au maximum

Vendredi 23 mai 2025 - 07:22  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in English


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : Intégrer de l'assembleur PowerPC dans un programme C
(Article écrit par Mathias Parnaudeau et extrait de GuruMed.net - novembre 2004)


Organisation d'un programme en assembleur, étude de cas

Recommandation : cet article suppose que vous ayez des bases en assembleur, notamment PowerPC. Pour ma part, je ne connais pas la signification de tous les mnémoniques mais leur nom répond à une certaine logique. L'important ici est d'avoir des notions sur la syntaxe assembleur et sur l'organisation d'un source. Il a, d'autre part, été uniquement réalisé et testé sur MorphOS.

L'utilisation de l'assembleur peut paraître superflue aujourd'hui, car les applications sur les machines MorphOS comme le Pegasos sont toutes quasiment instantanées, mais la curiosité est notre moteur :). Et puis, il paraît que les compilateurs C (dont GCC) ne sont pas forcément très optimisés pour le PowerPC. Donc autant savoir accélérer les routines qui en ont besoin.

Intrigué par l'assembleur PowerPC, j'ai tenté d'utiliser l'outil gratuit pasm de Frank Wille (maintenant nommé vasm, mais la syntaxe est propre à chaque assembleur et je n'ai pas trouvé d'exemples pour celui-ci. Le plus utilisé reste sans doute PowerASM de Haage & Partner. Il n'y avait plus qu'à se procurer des exemples. Et qui est mieux placé que vbcc pour générer du code pasm ? :)

Génération assembleur

J'ai donc essayé de comprendre avec un petit exemple en C "m1.c" transformé par la suite en assembleur :

#include 

int main(int argc, char **argv)
{
printf("Hello PPC world\n");
return 0;
}

Chaque compilateur C laisse la possibilité d'obtenir le code assembleur généré pour être ensuite assemblé, par GAS pour GCC ou par pasm pour vbcc. La compilation d'un programme C est en effet réalisée en deux opérations principales.

Voici ce que donne le code généré par GCC (avec la commande "gcc -S m1.c") :

	.file	"m1.c"
gcc2_compiled.:
	.section	".rodata"
	.align 2
.LC0:
	.string	"Hello PPC world\n"
	.section	".text"
	.align 2
	.globl main
	.type	 main,@function
main:
	stwu 1,-32(1)
	mflr 0
	stw 31,28(1)
	stw 0,36(1)
	mr 31,1
	stw 3,8(31)
	stw 4,12(31)
	lis 9,.LC0@ha
	la 3,.LC0@l(9)
	crxor 6,6,6
	bl printf
	li 3,0
	b .L6
.L6:
	lwz 11,0(1)
	lwz 0,4(11)
	mtlr 0
	lwz 31,-4(11)
	mr 1,11
	blr
.Lfe1:
	.size	 main,.Lfe1-main
	.ident	"GCC: (GNU) 2.95.3 20020615 (experimental/emm)"

Ce code peut être assemblé par GAS, mais on peut garder GCC comme interface de travail : "gcc -o m1gcc m1.s". On obtient alors l'exécutable qu'on aurait obtenu directement avec "gcc -o m1gcc m1.c".

Du côté de vbcc (taper la commande "vc -S m1.c"), on obtient m1.asm :

	.file	"m1.c"
#vsc elf
	.text
	.sdreg	13
	.global	main
	.align	4
main:
	mflr	11
	stw	11,4(1)
	stwu	1,-16(1)
	stw	3,8(1)
	stw	4,12(1)
	lis	3,.l2@ha
	addi	3,3,.l2@l
	bl	__v0printf
	mr	4,3
	li	3,0
.l1:
	addi	1,1,16
	lwz	11,4(1)
	mtlr	11
	blr
	.type	main,@function
	.size	main,$-main
# stacksize=16+??
	.align	2
	.section	.rodata
	.align	2
	.type	.l2,@object
	.size	.l2,17
.l2:
	.byte	72
	.byte	101
	.byte	108
	.byte	108
	.byte	111
	.byte	32
	.byte	80
	.byte	80
	.byte	67
	.byte	32
	.byte	119
	.byte	111
	.byte	114
	.byte	108
	.byte	100
	.byte	10
	.byte	0
	.globl	__v0printf

Là encore, on peut obtenir un exécutable avec la commande "vc -o m1vc m1.o". L'utilisation de pasm directement est suffisante pour créer le fichier objet mais pas l'exécutable, il manque le code de la fonction "vprintf". On obtiendrait un avertissement via une boîte de dialogue lors de l'exécution.

Analyse première

Quel que soit le source généré (par GCC ou vbcc), ça fleure bon l'assembleur PowerPC mais la syntaxe n'est pas du tout la même. Peut-on quand même obtenir des similitudes ? Des informations ? C'est ce que nous allons voir.

Et voilà ce qu'on constate :
  1. On reconnaît des déclarations semblables et un label "main" avec le code de la fonction. On distingue une section "text" pour du code et "data" pour les données.
  2. Pour le code pasm, d'après la documentation, le ".text" est bien l'équivalent d'un ".section text" avec des options particulières.
  3. Pour la fonction "main", des informations lui sont attribuées : globale, type function, calcul de la taille.
  4. On trouve des alignements différents, de deux ou quatre, et j'ignore encore pourquoi.
  5. Un commentaire s'effectue après un dièse (#), un essai sous GCC montre que c'est pareil. Ne pas utiliser le point virgule ! Je m'aperçois que la documentation de pasm m'apparaît déjà sous un autre angle et qu'il est bon de la relire. :)
  6. La chaîne de caractères est contenue dans la partie "data" du programme. Pour pasm, d'après la documentation (encore elle :)), on peut remplacer à la manière de GCC par : .string "Hello PPC world\n"
  7. L'appel à "printf" (et sans doute à toute fonction) est un peu obscur avec ses "lis" et "addi".
Bien, on commence à ne plus trouver ces sources si différents. Comme l'origine de l'article était la recherche d'informations pour programmer avec pasm, on laisse tomber GCC, mais continuez de faire des essais à titre personnel.

Où cela nous main ?

On va modifier légèrement "m1.c" pour que le "printf" reçoive en deuxième argument la variable "argc" : printf("Hello PPC world : %d\n", argc);. Le nouveau programme "m2.c" donne du code très peu différent, voyons ça :

# main de m1
main:
	mflr	11
	stw	11,4(1)
	stwu	1,-16(1)
	stw	3,8(1)
	stw	4,12(1)
	lis	3,.l2@ha
	addi	3,3,.l2@l
	bl	__v0printf
	mr	4,3
	li	3,0

# main de m2
main:
	mflr	11
	stw	11,4(1)
	stwu	1,-32(1)       # changement relatif à la taille de la pile
	stw	14,16(1)
	mr	14,3
	stw	4,12(1)
	mr	4,14
	lis	3,.l2@ha
	addi	3,3,.l2@l
	bl	printf
	mr	5,3
	li	3,0

On remarque deux choses : comme le "printf" se complexifie (gestion d'un paramètre), vbcc utilise la vraie commande "printf" au lieu de l'allégée maison "vprintf". La taille de la pile est passée à 32 et on devine que des sauvegardes de registres (14 et 4) se font dans la pile. Le paramètre "argc" serait dans r4 au moment de l'appel à "printf". Et à la fin du "main", on devine aisément un retour de 0 via r3.

Il est important d'effectuer de si petites modifications pour vraiment isoler les différences dans le code assembleur. Alors continuons...

Par où on double ?

Eh bien par une fonction destinée uniquement à remplir cette tâche. On modifie donc "m2.c" (ce qui nous donnera "m3.c", logique :)) en lui ajoutant une fonction qui retourne le double d'un entier passé en paramètre. On n'utilise pas la nouvelle commande, juste pour constater la différence de code généré. Comme on a dit, il faut aller doucement.

#include 

int doubleint(int a)
{
return (a*2);
}

int main(int argc, char **argv)
{
printf("Hello PPC world : %d\n", argc);
return 0;
}

Dans "m3.asm", voilà le code inséré après ".text" pour le code de la nouvelle fonction :

	.sdreg	13
	.global	doubleint
	.align	4
doubleint:
	stwu	1,-16(1)
	slwi	10,3,1
	mr	3,10
.l1:
	addi	1,1,16
	blr
	.type	doubleint,@function
	.size	doubleint,$-doubleint
# stacksize=16
#vsc elf

Encore une fois, que pouvons-nous tirer de ce code nouveau ? On constate qu'en fin de chaque fonction, un label réalise quelques opérations avant le retour "blr". On essaiera d'en savoir plus par la suite, mais ça a rapport avec la pile.

On retient autrement :
  • Que le passage de paramètre passe par r3... et que le retour de valeur se fait aussi par r3. Il faudra qu'on ajoute des paramètres pour voir s'ils sont passés logiquement par r4, r5, etc.
  • Que vbcc utilise un simple décalage pour la multiplication par 2, alors que GCC génère :

    	stw 3,8(31)
    	lwz 0,8(31)       # lecture dans r0 du paramètre
    	mr 9,0            # r9 = r0
    	add 0,9,0         # r0 = r0 + r9
    
Maintenant, appelons notre fonction dans le paramètre de "printf" qui devient dans un nouveau fichier "m4.c" :

printf("Hello PPC world : %d\n", doubleint(argc));

Le code de la fonction "main" devient :

main:
	mflr	11
	stw	11,4(1)
	stwu	1,-32(1)
	stw	14,16(1)
	mr	14,3
	stw	4,12(1)
	mr	3,14
	bl	doubleint
	mr	4,3
	lis	3,.l3@ha
	addi	3,3,.l3@l
	bl	printf
	mr	5,3
	li	3,0
.l2:
	lwz	14,16(1)
	addi	1,1,32
	lwz	11,4(1)
	mtlr	11
	blr

On a bien la valeur de "argc" dans r3, puis l'appel à "doubleint()", puis copie de r3 dans r4 qui est le deuxième argument à l'appel de "printf".

Parallèlement, un petit test m'a conduit à réaliser ma première optimisation (que d'émotions !), en remplaçant : "slwi 10,3,1" et "mr 3,10" par "slwi 3,3,1".

Ce n'est pas grand-chose, je l'avoue :). Et l'optimiseur réalise sans doute ça avec les options adéquates.

On y est !

Précédemment, on avait isolé le code de la fonction "doubleint". En le copiant dans un fichier "doubleint.asm" et en l'assemblant indépendamment (pasm doubleint.asm), on obtient un premier objet. Le deuxième est obtenu en conservant uniquement la fonction "main" de "m4.c" dans un fichier "m5.c" que l'on compile avec "vc -c m5.c". Les deux objets sont liés avec "vc -o m5vbcc m5.o doubleint.o". Et ça marche !

Le but est atteint : on sait désormais comment compiler des fonctions indépendantes en assembleur pour qu'elles puissent être utilisées dans un projet vbcc... ou GCC puisque ça fonctionne aussi !


[Retour en haut] / [Retour aux articles]