|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Il existe de nombreuses architectures de processeurs mais peu d'entre elles sont connues : l'idée commune est que les ordinateurs sont basés sur x86 et que les téléphones mobiles sont basés sur ARM. C'est vrai mais quoi d'autre ? Personne ne sait que certains décodeurs ou la PSP de Sony contiennent un MIPS. Certains se souviennent que la précédente génération d'ordinateurs Apple disposait de processeurs PowerPC mais ignorent que ceux-ci équipent toutes les consoles de nouvelle génération et sont utilisés dans l'aéronautique, la défense, les réseaux, les serveurs... Même si ces exemples prouvent que le PowerPC a de solides arguments en faveur d'une utilisation sérieuse, nous savons que dans les industries et les marchés de consommation, les technologies ne sont pas toujours choisies parce qu'elles sont les meilleures mais parce qu'elles sont moins chères ou mieux vendues. Dans les entreprises, il y a aussi des raisons historiques qui font que les ingénieurs restent avec les anciennes technologies modernisées, redoutés par un saut technologique. Quoi qu'il en soit, le PowerPC mérite d'être connu car plus que des qualités, il possède également des fonctionnalités spécifiques qui seront décrites dans cet article. Je voudrais souligner les points forts du PowerPC et ce qui le rend différent. Je montrerai d'abord en quoi c'est moderne, épuré... et ensuite les instructions montreront ce qui est singulier, avec des faits techniques bien sûr. Des comparaisons seront faites avec d'autres architectures RISC comme ARM et MIPS. Un autre objectif est de donner des clés pour améliorer la programmation du PowerPC tout en comprenant comment fonctionne le processeur et ce que font les compilateurs. Bases de l'architecture Même si elle est héritée de l'architecture POWER, elle avait été conçue comme une architecture nouvelle et épurée au début des années 1990 bénéficiant de dizaines d'années d'évolution des processeurs. Le PowerPC est un processeur RISC dont tous les concepts majeurs ont été spécifiés et implémentés dès le départ :
Comparons quelques points avec une autre architecture dite RISC, ARM qui est très populaire, elle est notamment embarquée dans tous les téléphones mobiles, choisie au départ pour sa faible consommation, qu'elle tend à ajouter sans cesse de nouvelles fonctionnalités. En 2010, il n'existe toujours pas de processeurs ARM 64 bits et le multicoeur arrive avec le Cortex-A9 (même s'il y avait auparavant une implémentation multicoeur avec l'ARM11MP). Pour donner quelques exemples et prouver la validité des choix PowerPC initiaux, ARMv5 a ajouté une instruction pour compter les zéros non significatifs (clz) mais de nombreuses autres fonctionnalités ont été ajoutées avec ARMv6 en 2002 : la gestion du boutisme ("setend" pour passer d'un mode à l'autre), l'accès exclusif aux données (ldrex/strex), une première implémentation SIMD (VFP, améliorée avec NEON sur ARMv7), des ASID pour les processus, un compteur de cycles (CCNT) qui n'est que de 32 bits... Le cache L2 est devenu un standard avec ARMv7. Pour tous ces exemples, on voit que le PowerPC vit avec ces fonctionnalités depuis le début ! Alors, en quoi le PowerPC est singulier ? Nous nous concentrerons sur le jeu d'instructions, qui est la partie la plus visible et la plus importante pour les programmeurs d'applications. Nous expliquerons également comment ces propriétés sont gérées par les compilateurs, avec des exemples pour illustrer comment ces instructions peuvent être utilisées. Enfin, lorsque cela est possible et parce qu'il est toujours intéressant de comparer, une note sera donnée sur d'autres architectures. # Prédiction de branchement statique Dans les programmes, les branchements doivent être évités car c'est un cauchemar pour le pipeline. C'est pourquoi les processeurs disposent parfois de mécanismes pour améliorer la prédiction dynamique avec des caches spécifiques, etc. Pour la prédiction de branchement statique, le PowerPC a quelques règles de base :
Il est difficile de savoir quelle est la politique d'un compilateur et quand le bit inverse est utilisé. Ceci est un exemple simple dans la fonction main :
Dans ce cas, les deux parties étant après la comparaison, argc est évalué avec la prédiction qu'il n'est pas égal à 1. Un deuxième exemple montre (dans le code assembleur généré par le compilateur) que le bloc est pris mais la raison est peut-être là. n'est qu'un bloc :
Sauf dans les boucles, il est difficile pour un compilateur de savoir quel bloc d'une instruction if/then doit être choisi en priorité. Pour vous donner une idée, GCC fournit une fonction intégrée appelée "__builtin_expect" qui est parfois utilisée dans des macros plus connues :
Ainsi, le compilateur peut réorganiser le code en déplaçant des blocs ou en inversant la condition de branchement (par exemple "eq" devenant "ne"). Il pourrait également utiliser le bit inverse. Sur ARM, une fonctionnalité intéressante existe pour éviter les branchements : les instructions conditionnelles. Cela signifie que les instructions sont suffixées avec la condition de branchement et sont exécutées en fonction de l'évaluation de cette condition :
# Registre de condition (CR) Le registre de condition est en fait un ensemble de huit champs de bits qui agissent indépendamment comme huit registres de condition, nommés de cr0 à cr7 ! Lors d'une comparaison, l'instruction code le champ qu'elle souhaite mettre à jour, par exemple :
Ici, cr7 est utilisé, donc le champ par défaut cr0 est conservé. Dans les boucles imbriquées, cela peut être très utile. De plus, il est possible d'appliquer des opérations logiques (and, or, nand, eqv...) entre les bits du registre de condition ! Par exemple, l'expression "do ... while ((a == 6) || (b == c))" pourrait s'écrire ainsi :
Ainsi, cela a permis d'économiser une branche conditionnelle, qui a toujours un coût en raison de la prédiction de branchement. Avec des comparaisons et des "cror" exécutés suffisamment tôt, la prédiction est calculée avant l'exécution du branchement, ce qui sauve à nouveau les cycles. Voici un exemple donné pour effectuer un changement avec un minimum de branches conditionnelles : wall.riscom.net/books/proc/ppc/cwg/code1.html. Il n'y a qu'un seul branchement conditionnel pour un commutateur ("switch") avec quatre cas allant vers le même code. Autre point qui n'est pas surprenant avec la philosophie RISC ("ne faites pas ce qui n'est pas nécessaire") : le registre de conditions n'est mis à jour que lorsque cela est demandé. La syntaxe est un point placé comme suffixe du nom de l'instruction. Sur MIPS, il n'y a pas de registre de conditions ! Dans ce cas, les branchements se font en comparant directement les valeurs des registres, par exemple "bge $4, $5, label" provoquera un saut vers le label si le contenu du registre $4 est supérieur ou égal à celui du registre $5. # Opérations sur les bits C'est l'une des plus grandes forces du PowerPC qui fournit de nombreuses instructions puissantes pour travailler sur les bits. Par exemple, "andc" effectue en un cycle une opération AND où le masque est un complément d'une valeur qui est une opération habituelle dans les programmes :
Sinon, les opérations sur les bits se font avec quelques instructions de la famille "rotation left and mask". "rlwinm" a cette syntaxe :
Il décale la valeur de rB de n bits et crée un masque allant du bit mB à mE. Ensuite, il applique une opération AND sur rB avec le masque donné et stocke les résultats dans rA. Cela permet simplement de décaler ou de faire pivoter une valeur mais aussi de conserver ou d'effacer un ensemble de bits. Une autre instruction est "rlwimi" dont le nom décrit ce qu'elle fait : faire pivoter le mot vers la gauche immédiatement puis insérer un masque. Avec une logique similaire à celle de l'exemple précédent, il insère un groupe de bits d'un mot dans un mot destination ! Un exemple typique est la gestion d'un mot contenant une valeur RVB, chaque composant pouvant être mis à jour indépendamment. L'instruction "rlwimi" est également utilisée par les compilateurs pour écrire des bits dans un champ de bits (en un cycle). Même si ce type de données est connu pour poser des problèmes de portabilité et doit être évité pour les interfaces externes, il peut s'avérer très utile et évite de nombreuses opérations : créer une valeur RVB ne nécessite que trois instructions. Comme d'habitude pour un processeur RISC, la rotation à droite n'a pas d'instruction spécifique car elle n'est pas nécessaire, il s'agit en fait d'une opération de rotation à gauche, ce qui est tout à fait logique. Pour faire pivoter une valeur de 5 bits vers la droite, utilisez :
Et bien sûr, le mnémonique "rotrwi" existe pour plus de commodité. Pour faire la même chose que le cas précédent avec une syntaxe plus explicite, il suffit d'écrire :
# Load-store avec octet inversé Même si le PowerPC peut être utilisé en mode petit boutiste, cela n'arrive pratiquement jamais. Mais il fournit des instructions puissantes pour charger et stocker des données 16 bits et 32 bits en échangeant les octets. Cela évite par exemple de charger une valeur dans un registre et d'appeler des opérations supplémentaires pour arriver au même résultat. Dans ce cas, si le compilateur ne fournit pas de composant intégré, il doit être utilisé en assembleur. Une fonction à jour (utilisable avec GCC 4.3.0 ou supérieur) est fournie sur un excellent site à l'adresse hardwarebug.org/2008/10/25/gcc-inline-asm-annoyance/ :
Le qualificatif "Z" étant récent, une autre syntaxe pourrait être :
Sinon, si un échange d'octets doit être effectué sans accès mémoire, les versions récentes de GCC fournissent une fonction intégrée appelée "__builtin_bswap32". S'il n'est pas disponible, il peut être codé ainsi pour échanger des octets dans le registre r3, avec quatre instructions :
Sur ARM, une instruction swap (rev) est apparue tardivement (ARMv6) et sur MIPS, elle est tout simplement absente. # Registre de comptage (CTR) Ce compteur permet de boucler un nombre de fois donné sans utiliser de GPR et sans modifier le registre de condition (CR). Par exemple, pour une boucle qui doit être exécutée 100 fois :
Ce type de boucle peut bénéficier d'une meilleure prédiction de branchement. Et il est facile à lire et facilement utilisable par les compilateurs. Une autre propriété curieuse mais intéressante est la possibilité d'utiliser CR pour accéder à l'adresse qu'il contient. Cela se fait par l'instruction "bcctr", comme "bclr" utilise LR. Il n'y a pas de registre PC (aussi appelé pointeur d'instruction) sur PowerPC, il n'est pas possible de jouer directement avec. Cela peut préserver la prédiction de branche. Mais un saut vers une adresse calculée peut être effectué avec CTR qui peut être utilisé dans l'instruction switch avec une table contenant des pointeurs vers lesquels accéder. Dans cet article, nous avons vu cinq fonctionnalités du jeu d'instructions PowerPC qui le différencient des autres architectures. Même si votre code n'est pas écrit en assembleur, nous savons comprendre comment ces instructions et mécanismes peuvent être utilisés avec un langage de niveau supérieur comme le C, avec l'aide du compilateur. Comme le PowerPC est moderne, la recherche sur l'optimisation était peut-être moins intensive par rapport à d'autres architectures, mais au cours des dernières années, le travail sur d'autres fonctionnalités intéressantes comme AltiVec et les instructions de cache (voir www.freevec.org) a montré que le PowerPC était peut-être envisagé à un moment donné, sans savoir quel était son plein potentiel. Espérons que le PowerPC soit encore au point, avec de nouveaux processeurs IBM (POWER7, 476FP...) et Freescale (famille QorIQ) qui sont de nouvelles occasions de diffuser et de faire connaître cette architecture puissante et singulière.
|