Introduction
Salut tout le monde. Bienvenue en novembre. J'espère que tu vas bien.
On a eu un mois d'octobre très chargé. On vient de lancer une grosse amélioration des solutions de la communauté. On les dédoublonne désormais, donc les solutions similaires n'apparaissent qu'une seule fois, on a ajouté de nouvelles options de tri, la possibilité de rechercher par code, et si tu regardes le C#, tu verras qu'on a aussi ajouté la possibilité de filtrer par différents concepts de programmation. Tu peux donc chercher des solutions qui utilisent le décalage de bits, la récursivité, ou tout ce qui te plaît. On déploiera tout ça sur les autres parcours pendant la semaine de la communauté.
Mais pour l'instant, concentrons-nous sur #12in23. Octobre a été un mois intéressant, consacré à l'exploration des langages orientés objet, mais ce mois-ci, on passe en mode hardcore. On se concentre sur les langages d'assemblage, et plus précisément sur l'assembleur MIPS, l'assembleur x86-64 et WebAssembly. Comme d'habitude, Erik va nous présenter ce qui rend ces langages intéressants et uniques.
Les badges
Comme toujours, tu peux gagner le badge Nibbly November en terminant 5 exercices, au choix, dans ces langages. On a aussi le badge Year-Long, et je sais que beaucoup travaillent à l'obtenir. Pour ça, on a 5 exercices en vedette à terminer. Les voici :
- Comptage de bits : compter les bits à 1 dans un nombre
- Grains : calculer le nombre de grains sur un échiquier dont les cases doublent
- Couleur des résistances : convertir la couleur d'une bande de résistance en sa représentation numérique
- Chiffre rotatif : implémenter le chiffre rotatif (aussi appelé chiffre de César)
- Comptage de nucléotides : calculer combien de fois chaque nucléotide apparaît dans une chaîne d'ADN
Contexte
Pourquoi ce nom, Nibble November ?
Eh bien, comme tu le sais probablement, un byte, c'est 8 bits. Et un nibble, c'est 4 bits. Le nom commence à avoir du sens si on remplace le y de byte par un i, ce qui donne bite. Un nibble, c'est donc une petite bouchée.
Qu'est-ce qu'un langage d'assemblage ?
Bon, commençons par quelques bases. Un processeur exécute des instructions, comme « additionner deux nombres » ou « décaler des bits vers la gauche », etc. Ces instructions sont appelées instructions de code machine, et ce ne sont rien de plus que des séquences de bits bien précises. Exécuter un programme, c'est « simplement » (entre guillemets) le processeur qui traite et exécute ces séquences de bits.
Comme il est fastidieux et source d'erreurs d'écrire directement les instructions sous forme de séquences de bits, Kathleen et Andrew Donald Booth ont conçu, dès 1947, un langage plus adapté à l'humain pour représenter les instructions de code machine. Un tel langage, qui représente les instructions de code machine, s'appelle un langage d'assemblage. Ce langage d'assemblage est ensuite converti en instructions de code machine par un « assembleur ».
Un aspect intéressant des langages d'assemblage, c'est qu'ils sont directement liés à une architecture de processeur, même s'ils sont généralement indépendants du système d'exploitation.
Au fait, si tu te souviens des cartes perforées, ces grandes cartes utilisées dans les tout premiers ordinateurs pour leur faire exécuter des programmes, elles aussi étaient un langage d'assemblage !
En quoi le langage d'assemblage diffère-t-il des langages avec lesquels on programme aujourd'hui ?
La différence essentielle, c'est que les langages d'assemblage sont de très bas niveau. Tu vas devoir te passer de beaucoup d'abstractions auxquelles tu t'es habitué. Des classes ou des objets ? Introuvables. Des boucles ? Tu devras les écrire manuellement, à l'aide de sauts. Des fonctions ? Non ! J'ai trouvé ça très formateur d'écrire du code en assembleur, parce qu'on réalise alors à quel point les langages modernes nous facilitent la vie. Mais écrire du code en assembleur est aussi extrêmement utile, car on comprend bien mieux comment les choses fonctionnent réellement.
Anecdote : 99 % du code source de RollerCoaster Tycoon était du code assembleur écrit à la main ! C'est une prouesse stupéfiante, que tu apprécieras encore plus une fois que tu auras écrit un peu d'assembleur toi-même.
Est-ce qu'on écrit encore de l'assembleur ?
Eh bien, moins qu'avant. Autrefois, l'assembleur écrit à la main surpassait souvent le code machine généré par un compilateur (c'est d'ailleurs l'une des raisons pour lesquelles le C++ permet d'intégrer directement du code assembleur), mais les compilateurs sont devenus si doués pour générer du code machine que c'est rarement vrai aujourd'hui. Cela dit, on trouve encore de l'assembleur dans les environnements où la performance est critique ou les ressources limitées.
Aperçus
MIPS
- MIPS (Microprocessor without Interlocked Pipelined Stages) est une famille d'architectures de jeux d'instructions de type RISC (ordinateur à jeu d'instructions réduit)
- Développée par MIPS Computer Systems et publiée pour la première fois en 1985
- Plusieurs versions : MIPS I, II, III, IV, V et MIPS32/64. Les deux premières versions étaient uniquement 32 bits, mais MIPS III a introduit la prise en charge du 64 bits.
- Plusieurs extensions facultatives, comme les instructions SIMD et la compression
- Très influente sur les architectures RISC ultérieures
- MIPS a annoncé en 2021 que l'architecture MIPS n'était plus développée et s'est tourné vers RISC-V (une architecture open source et libre de droits)
- Principalement utilisée dans les systèmes embarqués (par ex. les routeurs) et les serveurs (les ordinateurs Silicon Graphics l'utilisaient, célèbres pour les effets spéciaux au cinéma), le supercalculateur NEC Cenju-4, la voiture Tesla Model S, la sonde New Horizons de la NASA, mais aussi pour enseigner l'assembleur dans les universités et dans plusieurs consoles de jeu (par ex. la PlayStation d'origine, la PlayStation Portable et la Nintendo 64)
Assembleur x86-64
- Conçu par AMD et publié en 1999 sous le nom d'architecture AMD64
- C'est une version 64 bits du jeu d'instructions x86, qui remonte à 1978, quand Intel a lancé son microprocesseur 8086. C'était un processeur 16 bits, mais plus tard, le 80386 a ajouté des instructions 32 bits, et ce jeu d'instructions est devenu synonyme de x86.
- L'apport essentiel du 64 bits a été de pouvoir adresser davantage de mémoire (l'adressage 32 bits est limité à 4 Go), ce qui était devenu un goulot d'étranglement. Le 64 bits peut, en théorie, adresser 16 exaoctets, mais seuls 48 bits sont utilisés pour l'instant, ce qui permet d'adresser 256 To (extensible plus tard si besoin)
- AMD64 étend le jeu d'instructions x86 et a été conçu pour être entièrement compatible avec les applications 16 et 32 bits existantes, via un mode de compatibilité
- Intel avait conçu l'IA-64 sans qu'AMD soit impliqué. C'était un jeu d'instructions 64 bits nouveau, très différent et non rétrocompatible. Finalement, c'est AMD64 qui l'a emporté, et Intel a implémenté sa propre version (avec seulement de légères différences sémantiques)
- Utilisé partout. Des stations de travail aux serveurs (y compris les supercalculateurs), des systèmes embarqués aux consoles de jeu (par ex. la PS5 et la Xbox Series X).
WebAssembly
- Conçu par le W3C, un organisme de normalisation des technologies web
- Ses objectifs de conception sont :
- Rapide, sûr et portable
- Une représentation efficace et portable
- Autrefois, l'exécution rapide sur le web passait généralement par des plugins de navigateur spécialisés, comme Flash et Silverlight, car JavaScript se prête assez mal au calcul haute performance. Les principaux inconvénients de ces plugins étaient qu'ils souffraient souvent de nombreux problèmes de sécurité et n'étaient pas standardisés.
- Mozilla a conçu asm.js, un sous-ensemble de JavaScript qui visait à permettre d'exécuter du code dans le navigateur avec d'excellentes performances, ce qu'il obtenait grâce à la cohérence des types (pas de changement dynamique de type) et à l'absence de ramasse-miettes. Les langages pouvaient alors être compilés vers asm.js tout en conservant de bonnes performances sur le web. Mais ça restait du JS, donc limité dans ce qu'on pouvait faire. D'où la proposition d'un nouveau langage : WASM.
- Un langage proche de l'assembleur, en ce qu'il fournit un ensemble d'instructions à exécuter. Point crucial : il n'est pas lié à un processeur particulier, il est donc indépendant de la plateforme et nécessite une implémentation pour chacune (une machine virtuelle). Cela signifie que WebAssembly est en fait du bytecode, pas du code machine
- Typé statiquement (différence cruciale avec JS)
- Utilise généralement une compilation anticipée (AOT) ou à la volée (JIT) (mais peut être interprété)
- Standard ouvert qui définit deux choses :
- Un format binaire
- Un format texte (qui est compilé vers le format binaire)
- Des implémentations dans tous les principaux navigateurs
- Utilisé dans de nombreuses pages web qui exigent de hautes performances, comme Google Earth, Figma, Unity ou Autocad. Gagne aussi du terrain côté serveur, par ex. pour exécuter des microservices, tourner sur des plateformes SaaS (par ex. les workers CloudFlare) ou dans Docker
Et d'un point de vue programmation, en quoi diffèrent-ils ?
MIPS
- Utilise une architecture load-store (aussi appelée registre-registre), où les instructions font soit des accès mémoire, soit de l'arithmétique, mais opèrent exclusivement sur des données se trouvant dans des registres
Assembleur x86-64
- Utilise une architecture registre-mémoire, qui permet d'effectuer des opérations sur (ou depuis) la mémoire, ainsi que sur les registres
WebAssembly
- Utilise une programmation à pile (sans registres), avec la possibilité de lire des données depuis la mémoire et d'y écrire
Qu'est-ce qui rend ces langages géniaux ?
MIPS
- Compact. Le jeu d'instructions MIPS tient sur une seule page
- Des conventions d'appel bien établies aident à savoir comment utiliser les registres disponibles, par ex. lesquels servir à passer les arguments et lesquels à renvoyer les résultats.
- Stable. La dernière version date de 2014
- Abondamment documenté, notamment dans les ouvrages universitaires
- Très utilisé dans le monde réel. Des milliards d'appareils
Assembleur x86-64
- Bien qu'étendant le x64, il a apporté beaucoup de nouvelles fonctionnalités, notamment :
- La prise en charge des entiers 64 bits
- Des registres supplémentaires
- Les instructions SSE (instructions vectorielles)
- L'accès relatif aux données (plus efficace avec les bibliothèques partagées)
- Le bit NX (No-Execute), une fonctionnalité de sécurité qui empêche l'exécution de code dans certaines pages de mémoire
- Familier. Comme il étend le jeu d'instructions x86, il sera relativement facile à apprendre pour ceux qui connaissent déjà ce jeu d'instructions Une documentation vaste et détaillée
- Stable. Bien que de nouvelles versions soient ajoutées régulièrement, le cœur reste extrêmement stable et rétrocompatible
WebAssembly
- La machine virtuelle WebAssembly, basée sur une pile, est légère et simple comparée aux langages d'assemblage des processeurs physiques (y compris RISC). Ce qui rend la compilation vers cette cible relativement simple.
- Le format texte de WebAssembly utilise le « sucre » des S-expressions pour obtenir un style impératif familier, qui est ensuite transformé en code à base de pile. La forme avec les S-expressions est appelée la « forme sucrée » et se « désucre » vers l'autre forme, équivalente à ce qui se trouve dans le binaire. Les S-expressions seront familières à quiconque a déjà travaillé avec un LISP
- Une forte interopérabilité avec JavaScript. Il est simple de passer des données depuis et vers JavaScript. Mise en garde importante : WASM ne permet pas (encore) d'interagir avec le DOM
- En constante amélioration. Non seulement les machines virtuelles WASM s'améliorent, mais la norme elle-même est en développement actif. Des tonnes de nouvelles fonctionnalités sont conçues et développées, comme les instructions liées à SIMD, le ramasse-miettes, les threads, les optimisations des appels terminaux, et bien d'autres
- Sûr. Le code est validé et exécuté dans un bac à sable, ce qui offre un degré de validation statique supérieur à celui de JavaScript ou des langages d'assemblage natifs. La sémantique est bien définie, ce qui la rend facile à vérifier et à analyser
Points forts
MIPS
- Efficacité. Les processeurs MIPS sont très efficaces, ce qui les rend parfaits pour les systèmes embarqués.
- Performance. D'excellentes performances, d'où l'utilisation de MIPS dans les supercalculateurs
- Facilité d'apprentissage. Avec peu d'instructions et chaque instruction ne faisant qu'une seule chose simple, il est facile à apprendre. Idéal pour l'enseignement.
Assembleur x86-64
- Puissant. Le x86-64 a été peaufiné pendant des décennies, avec une multitude d'instructions qui améliorent les performances. Un exemple : SIMD (Single Instruction, Multiple Data), des instructions qui, disons, permettent à une seule instruction de s'exécuter en parallèle sur plusieurs données.
- Omniprésent. Les appareils qui exécutent du x86-64 sont partout. Les processeurs Intel et AMD l'implémentent. C'est le standard de fait depuis un bon moment
- Mis à jour régulièrement. Par ex. de nouvelles instructions vectorielles, avec SSE3-5, AVX, AVX-512 et plus encore
WebAssembly
- Efficace. Le format binaire est compact et peut être décodé, validé et compilé en une seule passe rapide. Il peut aussi être traité en flux, ce qui permet de commencer le décodage, la validation et la compilation le plus tôt possible, avant même d'avoir vu toutes les données. Et il est également parallélisable. Ce qui le rend parfait pour les applications web à hautes performances.
- Une excellente cible de compilation. Permet à du code écrit dans de nombreux langages de tourner sur le web. La plupart des grands langages prennent en charge la compilation vers des binaires WebAssembly, ce qui signifie que ton code peut tourner sur le web sans avoir à écrire de JavaScript. Certains langages ne compilent pas ton code vers WebAssembly, mais compilent à la place le runtime vers WebAssembly, qui peut alors exécuter du bytecode inchangé.
- Facile à déployer et à exécuter. Il suffit d'une machine virtuelle capable d'interpréter le bytecode, et tous les principaux navigateurs en intègrent une.
- Pas seulement lié au web : il peut aussi tourner côté serveur. WebAssembly System Interface (WASI) est une interface (ABI et API) conçue pour être portable sur n'importe quelle plateforme. Elle est similaire à POSIX (des interfaces standard pour les systèmes Unix) et fournit des choses comme les entrées-sorties. La sécurité est un élément clé de sa conception, qui inclut l'exécution en bac à sable et une approche par capacités (il faut demander explicitement les autorisations pour des choses comme les fichiers ou les sockets). WASI pourrait aussi permettre une interconnexion facile entre langages. Solomon Hykes, cofondateur de Docker, écrivait en 2019 : « Si WASM+WASI avait existé en 2008, nous n'aurions pas eu besoin de créer Docker »
Que choisir
- Si tu n'as jamais travaillé avec un langage d'assemblage, WebAssembly est probablement le plus facile pour débuter. Cela dit, si tu veux apprendre un langage d'assemblage qui se compile en code machine, essaie l'assembleur MIPS
- Si tu travailles sur une machine x86-64 (très probable), essaie l'assembleur x86-64
- Si tu connais LISP, tu apprécieras le fait que WebAssembly utilise les S-expressions
- Si tu travailles avec des applications web, WebAssembly est l'option la plus logique
- Si la performance t'importe, x86-64 et MIPS sont d'excellentes options. Et si c'est la performance web qui t'importe, essaie WebAssembly