Je lis le livre The Elements of Computing Systems: Building a Modern Computer from First Principles , qui contient des projets englobant la construction d'un ordinateur à partir de portes booléennes jusqu'aux applications de haut niveau (dans cet ordre) . Le projet en cours sur lequel je travaille est l'écriture d'un assembleur utilisant un langage de haut niveau de mon choix, pour traduire du code Hack Assembly en code machine Hack (Hack est le nom de la plateforme matérielle construite dans les chapitres précédents). Bien que le matériel ait tous été construit dans un simulateur, j'ai essayé de prétendre que je construis vraiment chaque niveau en utilisant uniquement les outils dont je disposais à ce stade du processus réel.
Cela dit, cela m'a fait réfléchir. Utiliser un langage de haut niveau pour écrire mon assembleur est certainement pratique, mais pour le tout premier assembleur jamais écrit (c'est-à-dire dans l'histoire), ne devrait-il pas être écrit en code machine, puisque c'est tout ce qui existait à l'époque?
Et une question corrélée ... et aujourd'hui? Si une toute nouvelle architecture CPU sort, avec un tout nouveau jeu d'instructions et une toute nouvelle syntaxe d'assemblage, comment l'assembleur serait-il construit? Je suppose que vous pouvez toujours utiliser un langage de haut niveau existant pour générer des binaires pour le programme assembleur, car si vous connaissez la syntaxe des langages d'assemblage et de machine pour votre nouvelle plate-forme, alors la tâche d'écrire l'assembleur est vraiment juste un tâche d'analyse de texte et n'est pas intrinsèquement liée à cette plate-forme (c'est-à-dire ayant besoin d'être écrite dans le langage machine de cette plate-forme) ... c'est la raison même pour laquelle je suis capable de "tricher" lors de l'écriture de mon assembleur Hack en 2012, et d'utiliser certains préexistants langage de haut niveau pour m'aider.
pour le tout premier assembleur jamais écrit (c'est-à-dire dans l'histoire), n'aurait-il pas besoin d'être écrit en code machine
Pas nécessairement. Bien sûr, la toute première version v0.00 de l'assembleur doit avoir été écrite en code machine, mais elle ne serait pas suffisamment puissante pour être appelée assembleur. Il ne supporterait même pas la moitié des fonctionnalités d'un "vrai" assembleur, mais il suffirait d'écrire la prochaine version de lui-même. Ensuite, vous pouvez réécrire la v0.00 dans le sous-ensemble du langage d'assemblage, l'appeler v0.01, l'utiliser pour créer le prochain ensemble de fonctionnalités de votre assembleur v0.02, puis utiliser la v0.02 pour construire la v0.03, et ainsi de suite, jusqu'à ce que vous arriviez à la v1.00. En conséquence, seule la première version sera en code machine; la première version publiée sera dans la langue de l'assembly.
J'ai démarré le développement d'un compilateur de langage de modèle en utilisant cette astuce. Ma version initiale utilisait des instructions printf
, mais la première version que j'ai mise à utiliser dans mon entreprise utilisait le processeur de modèle même qu'elle traitait. La phase d'amorçage a duré moins de quatre heures: dès que mon processeur pouvait produire une sortie à peine utile, je l'ai réécrite dans sa propre langue, compilée et jeté la version non basée sur des modèles.
Selon Wikipedia, le tout premier assembleur/langage d'assemblage a été implémenté pour l'IBM 701 par Nathaniel Rochester . (Les dates sont un peu incertaines de l'article de Wikipedia. Il indique que Rochester a rejoint IBM en 1948, mais une autre page de Wikipédia indique que le 701 a été annoncé publiquement en 1952. Et cette page d'IBM indique que "[a] la conception actuelle a commencé le 1er février 1951 et s'est terminée un an plus tard" .)
Cependant, "Assembleurs et chargeurs" de David Salomon déclare (à la page 7) qu'EDSAC avait également un assembleur:
"L'un des premiers ordinateurs à programme stocké a été l'EDSAC (Electronic Delay Storage Automatic Calculator) développé à l'Université de Cambridge en 1949 par Maurice Wilkes et W. Renwick. Dès ses premiers jours, l'EDSAC avait un assembleur, appelé Initial Orders. Il a été implémenté dans une mémoire morte formée d'un ensemble de sélecteurs de téléphone rotatifs, et il a accepté des instructions symboliques. Chaque instruction se composait d'un mnémonique d'une lettre, d'une adresse décimale et d'un troisième champ qui était un lettre. Le troisième champ a provoqué l'ajout d'une des 12 constantes prédéfinies par le programmeur à l'adresse au moment de l'assemblage. " (Références omises ... voir l'original.)
En supposant que nous acceptions que les "commandes initiales" aient priorité, nous avons des preuves claires que le premier assembleur a été implémenté dans le code machine.
Ce modèle (écrire les assembleurs initiaux en code machine) aurait été la norme jusque dans les années 1950. Cependant, selon Wikipedia , "[a] les assembleurs ont été les premiers outils de langage pour bootstrap eux-mêmes". Voir aussi cette section qui explique comment un assembleur primordial a écrit le code machine pour bootstrap un assembleur plus avancé qui a été codé en langage assembleur.
De nos jours, les assembleurs et les compilateurs sont écrits dans des langages de niveau supérieur, et un assembleur ou un compilateur pour une nouvelle architecture de machine est généralement développé sur une architecture différente et compilé de manière croisée.
(FWIW - l'écriture et le débogage de programmes non triviaux en code machine est un processus extrêmement laborieux. Quelqu'un développant un assembleur en code machine serait très probablement bootstrap à un assembleur écrit en assembleur dès que possible. )
Cette page Wikipédia sur compilateurs et assembleurs d'amorçage vaut la peine d'être lue ... si tout cela vous déroute.
Je suppose que les premiers assembleurs ont été écrits en code machine, car comme vous le dites, rien d'autre n'était disponible à l'époque.
Aujourd'hui, cependant, lorsqu'une toute nouvelle architecture CPU sort, nous utilisons ce que l'on appelle n Cross-Compiler , qui est un compilateur qui produit du code machine non pas pour l'architecture sur laquelle il s'exécute, mais pour une architecture différente.
(En fait, comme je suis sûr que vous le découvrirez plus loin dans le livre que vous lisez, il n'y a absolument rien qui rend un compilateur intrinsèquement plus approprié pour produire du code machine pour l'architecture sur laquelle il s'exécute que sur n'importe quel autre autre architecture. Il s'agit simplement de l'architecture que vous, en tant que créateur du compilateur, allez cibler.)
Donc, aujourd'hui, il est même possible (au moins en théorie) de créer une toute nouvelle architecture et d'avoir des compilateurs de langage de haut niveau s'exécutant nativement dessus (compilés sur d'autres architectures à l'aide de compilateurs croisés) avant même d'avoir un assembleur pour cette architecture.
Au début, "Assembly" était écrit sur papier, puis "compilé" manuellement sur des cartes perforées.
Mon grand-père travaillait avec un ZRA1 (désolé, la page n'existe qu'en allemand, mais la traduction de Google est ok au point où vous pouvez réellement ramasser les faits les plus importants: D).
Le modus operandi était d'écrire votre code sur papier dans une sorte de langage d'assemblage et le secrétaire ferait la transcription des cartes perforées, puis les passerait à l'opérateur et le résultat serait rendu le lendemain matin .
Tout cela était essentiellement avant que les programmeurs n'aient le luxe de saisir des données via un clavier et de les afficher sur un écran.
Il est difficile d'être certain du premier assembleur très (difficile même de définir ce que c'était). Il y a des années, quand j'écrivais quelques assembleurs pour des machines qui manquaient d'assembleurs, j'écrivais encore le code en langage assembleur. Ensuite, après avoir eu une section de code raisonnablement complète, je l'ai traduite à la main en code machine. Il s'agissait cependant de deux phases entièrement distinctes - lorsque j'écrivais le code, je ne travaillais pas ou ne pensais pas du tout au niveau du code machine.
Je dois ajouter que dans quelques cas, je suis allé plus loin: j'ai écrit la plupart du code dans un langage d'assemblage que j'ai trouvé plus simple à utiliser, puis j'ai écrit un petit noyau (plus ou moins ce que nous appellerions maintenant une machine virtuelle) d'interpréter cela sur le processeur cible. C'était mortellement lent (en particulier sur un processeur 1 MHz, 8 bits), mais cela n'avait pas beaucoup d'importance, car il ne fonctionnait normalement qu'une seule fois (ou tout au plus quelques fois).
Vous n'avez pas besoin d'un assembleur pour assembler à la main le code du langage d'assemblage dans le code machine. Tout comme vous n'avez pas besoin d'un éditeur pour écrire le code du langage d'assemblage.
Les premiers assembleurs ont probablement été écrits en langage assembleur puis assemblés à la main en code machine. Même si le processeur n'avait pas de "langage d'assemblage" officiel, les programmeurs ont probablement fait la majeure partie du travail de programmation en utilisant une sorte de pseudo-code avant de traduire ce code en instructions machine.
Même dans les premiers jours de l'informatique , les programmeurs ont écrit des programmes dans une sorte de notation symbolique et les ont traduits en code machine avant de l'introduire dans leur ordinateur. Dans le cas d'Augusta Ada King, elle aurait dû les traduire en cartes perforées pour Babbage 's Moteur analytique , mais hélas il n'a jamais été construit .
Le premier ordinateur que je possédais était un Sinclair ZX81 (Timex 1000 aux États-Unis). Le dos du manuel contient toutes les informations dont vous avez besoin pour traduire le langage d'assemblage Z80 en code machine (y compris même tout le mode d'index bizarre opcodes le Z80 avait).
J'écrirais un programme (sur papier) en langage assembleur et ferais un essai à travers le code. Lorsque j'étais satisfait que mon programme était exempt de bogues, je cherchais chaque instruction à la fin du manuel, la traduisais en code machine et écrivais le code machine sur le papier également. Enfin, je tapais toutes les instructions du code machine dans mon ZX81 avant de l'enregistrer sur bande et d'essayer de l'exécuter.
Si cela ne fonctionnait pas, je revérifierais mon assemblage à la main et si une traduction était incorrecte, je patchais les octets chargés à partir de la bande avant de la ré-enregistrer et d'essayer à nouveau d'exécuter le programme.
Par expérience, je peux vous dire qu'il est beaucoup plus facile de déboguer votre code s'il est écrit en langage assembleur qu'en code machine - d'où la popularité des désassembleurs. Même si vous n'avez pas d'assembleur, l'assemblage manuel est moins sujet aux erreurs que d'essayer d'écrire directement du code machine, bien que je suppose que n vrai programmeur comme Mel pourrait ne pas être d'accord. * 8 ')
Il n'y a pas de différence à ce moment ou maintenant. Vous souhaitez inventer un nouveau langage de programmation, vous choisissez l'un des langages dont vous disposez aujourd'hui pour réaliser le premier compilateur. sur une certaine période de temps, si c'est un objectif du projet, vous créez un compilateur dans ce langage et il peut alors s'auto-héberger.
Si vous n'aviez que du crayon et du papier et des commutateurs ou des cartes perforées comme interface utilisateur pour le premier ou le nouveau jeu d'instructions, vous avez utilisé un ou tous les éléments disponibles. Vous pourriez très bien avoir écrit un langage d'assemblage, sur papier, puis utilisé un assembleur, vous, pour le convertir en code machine, peut-être en octal, puis à un moment donné qui est entré dans l'interface de la machine.
Lorsqu'un tout nouveau jeu d'instructions est inventé aujourd'hui, pas différent, selon l'entreprise/les individus, les pratiques, etc., il est fort probable que l'ingénieur matériel programmant probablement en verilog ou vhdl, écrit les premiers programmes de test à la main en code machine (probablement en hexadécimal ou binaire). en fonction des progrès des équipes logicielles elles peuvent très rapidement ou non pour très longtemps passer en langage assembleur puis en compilateur.
Les premières machines à calculer n'étaient pas des machines à usage général que vous pouviez utiliser pour créer des assembleurs et des compilateurs à partir de. Vous les avez programmés en déplaçant des fils entre la sortie de l'ALU précédente et l'entrée de la suivante. Finalement, vous aviez un processeur à usage général tel que vous pouviez écrire un assembleur dans Assembly, l'assembler à la main, le nourrir en code machine, puis l'utiliser pour analyser ebcdic, ascii, etc., puis auto-héberger. stocker le binaire sur un support que vous pourrez lire/charger plus tard sans avoir à basculer les commutateurs vers le code machine d'alimentation manuelle.
Pensez aux cartes perforées et à la bande de papier. Au lieu de basculer les commutateurs, vous pourriez très certainement fabriquer une machine entièrement mécanique, un dispositif d'économie de travail, qui a créé le support que l'ordinateur lirait. Au lieu d'avoir à entrer les bits de code machine avec des commutateurs comme un altair, vous pouvez plutôt alimenter des bandes de papier ou des cartes perforées (en utilisant quelque chose de mécanique, pas de processeur, qui alimentait la mémoire ou le processeur, OR en utilisant un petit chargeur de démarrage écrit avec un code machine). Ce n'était pas une mauvaise idée car vous pourriez créer quelque chose, piloté par l'ordinateur, qui pourrait également produire mécaniquement les bandes de papier ou les cartes perforées, puis les réintroduire. Deux sources de cartes perforées, le dispositif d'économie de main-d'oeuvre mécanique non informatisé et la machine entraînée par ordinateur produisent tous les deux des "fichiers binaires" pour l'ordinateur.
Il y a un ou deux exemples dans Brook's Computer Zoo où il a dit quelque chose comme "les mnémoniques sont notre invention, le concepteur a simplement utilisé l'opcode numérique ou le caractère dont le code était l'opcode", donc là où les machines pour qu'il n'y avait même pas de langue d'assemblée.
La saisie des programmes met fin au débogage sur le panneau avant (pour ceux qui ne l'ont pas fait, c'était un moyen de configurer la mémoire, vous définissez certains commutateurs sur l'adresse, d'autres sur la valeur et appuyez sur un bouton, ou un autre bouton pour lire la valeur) était courant bien plus tard. Certains anciens minuteries se vantent de pouvoir encore saisir le code de démarrage des machines qu'ils ont largement utilisées.
La difficulté d'écrire directement du code machine et de lire des programmes à partir d'un vidage de mémoire dépend du langage machine, certains d'entre eux sont relativement faciles (la partie la plus difficile est le suivi des adresses), x86 est l'un des pires.
J'ai construit un ordinateur en 1975. Il était très avancé par rapport à son contemporain l'Altair, car il avait une "moniteur rom" qui me permettait d'entrer des programmes en tapant le code machine en hexadécimal et en visualisant ce code sur un moniteur vidéo, où comme avec le Altair chaque instruction machine devait être entrée un peu à la fois à l'aide d'une rangée de commutateurs.
Alors oui, au tout début des ordinateurs, puis au début des ordinateurs personnels, les gens écrivaient des applications en code machine.
Une anecdote:
Quand j'ai appris le langage d'assemblage, sur le Apple] [ il y avait un programme inclus dans le ROM appelé le micro-assembleur. Il a fait la traduction immédiate de l'assemblage en octets, au fur et à mesure que vous les saisissiez. Cela signifie qu'il n'y avait pas d'étiquettes - si vous voulez sauter ou charger, vous deviez calculer les décalages vous-même. C'était beaucoup plus facile que de rechercher les dispositions d'instructions et de saisir des valeurs hexadécimales.
Sans doute, les vrais assembleurs ont d'abord été écrits à l'aide du micro-assembleur, ou d'un autre environnement pas tout à fait complet.