Klein leed en moedig voorwaarts
Arnout van Kempen schrijft wekelijks over wat hij noemt "rommelen in een digitale wereld". Deze week alweer editie 100 van zijn 'nerd-rubriek' voor liefhebbers.
Het programmaatje dat we vorige keer maakten, zal wellicht wat hoofdbrekens hebben gekost. Mij in ieder geval wel. Een paar kleinigheden vooral:
Het @ symbool wordt door mijn assembler op de Pi niet herkend als je doel CPU een generieke ARM CPU voor de Pi 5 is. Mogelijk werkt het wel voor andere ARM CPU’s, maar niet altijd dus. Wat wel zou moeten werken is een # als het commentaar begint aan het begin van de regel en // als het na een commando volgt.
Verder ben ik niet erg blij met de syntax highlighting die verschillende editors bieden. Helix bakt er niets van. Geany vind ik onhandig omdat het in de GUI werkt, dus ik ben weer teruggekeerd naar nano. Maar dat heeft instellingen waar ik ook niet helemaal gelukkig mee ben. Gelukkig is dat via een alias in de Z shell goed op te lossen. Om te voorkomen dat ik nano nergens anders meer voor kan gebruiken, heb ik een specifieke alias gemaakt voor werken met assembler, met een verwijzing naar de eerste assembler waar ik ooit mee werkte, EDTASM. De alias heb ik in de .zshrc file geplaatst en ziet er als volgt uit:
alias edtasm=‘nano -ilY asm —tabsize=4’
Let op, vóór de tabsize staat een dubbel streepje! En wie mee doet via GitHub zal zien dat daar een bestand staat zonder commentaar, dus dat zou altijd moeten werken. Tenslotte nog de opmerking dat een assembler-bestand vaak herkend wordt door de .s of door .asm als extensie, beide is prima. Net zoals de keuze voor een editor vooral een kwestie van smaak is.
Terug naar de inhoud. We hebben een extreem simpel assembly programmaatje geschreven dat vrijwel al het werk door Linux liet doen, alles liep immers via een systemcall. Willen we meer, dan zullen we moeten begrijpen hoe de ARM CPU in elkaar zit. Ik gaf al aan dat ARM-architectuur RISC is, dat betekent dat we vrij weinig instructies hebben, die wel razendsnel verwerkt worden. Een ander typisch kenmerk van RISC is een grote hoeveelheid registers. Dat verkleint het aantal data-bewegingen tussen CPU en geheugen en dat geeft weer extra snelheid. Wat zijn nu de registers van een ARM? Nou, vrij veel.
Om te beginnen kent de ARM 31 registers van 64 bits breed. Deze heten x0 tot en met x30. Enkele daarvan hebben specifieke rollen, waar we nu niet op in gaan. Ga er van uit dat x0-x7, x9-x15 en x19-x28 vrij te gebruiken zijn. X30 bevat het return adres bij aanroep van een subroutine en dat betekent dat je bij een call binnen een call x30 op de stack zal moeten bewaren, anders raak je bij de returns de weg kwijt.
Ieder xn register van 64bits kan je ook benaderen als een 32 bits adres, met een wn. De hoogste 32 bits worden dan genegeerd of op nul gezet. Dus w2 is de onderste 32 bits van x2.
Vervolgens heet de ARM een SP om de positie van de stack bij te houden en een PC om de volgende programma-instructie aan te wijzen. Niets bijzonders. Wel bijzonder is XZR/WZR, het 64-bits en 32-bits Zero Register. Dit register geeft 0 als je het uitleest en als je er naar schrijft gebeurt er niets.
Is dat handig? Ja, dat is handig in een RISC-omgeving. In RISC wil je minimale interacties met het geheugen, geen gekkigheden met allerlei adresseringsmethodes voor het geheugen. En je wilt ook dat alle instructies zoveel mogelijk hetzelfde formaat hebben, zodat fetchen en decoden efficient gebeurt.
Vergelijk wat er gebeurt als je op een 8086 wil controleren of de waarde in BX gelijk is aan nul, of datzelfde op een ARM voor x1. Eerst de 8086:
CMP BX, 0 ; Vergelijk BX met 0
JE BX_IS_ZERO
En op de ARM:
CMP X1, XZR // Vergelijk X1 met 0 (XZR is altijd nul)
B.EQ X1_IS_ZERO
Dat lijkt hetzelfde. Immers, we doen in beide gevallen een CoMPare van het register met waarde 0 en dan een Jump if Equal, of Branche if EQual. Wat is dan het verschil? De 8086 instructie bevat het getal 0, dat als onderdeel van de instructie uit het geheugen geladen moet worden. Dat kost tijd. De ARM instructie bevat het Zero Register dat altijd al aanwezig is in de CPU, dat dus niet in de instructie staat en ook niet uit het geheugen hoeft te worden gehaald. En dat is dus net iets efficiënter.
Maar we zijn er nog niet. We hebben nu 31 algemene registers gezien, waarvan een aantal specifieke functies hebben, en nog drie echt specifieke registers: SP, PC en XZR. Maar de ARM kent ook nog V0-V31, dat zijn 32 floating point registers, die op verschillende manieren ingezet kunnen worden voor rekenwerk met wetenschappelijke precisie. Leuk voor renteberekeningen, ROI, contante waarde en wat al niet.
Dan zijn er nog de vlaggen in het PSTATE, Processor State Register, en drie specifieke registers voor de ARM variant van protected mode, waardoor in een modern operating system kan worden gewerkt.
Wie mee wil doen met #klooienmetcomputers kan dat doen via GitHub. Maak een account op github.com en zoek naar Abmvk/kmc. Het account Abmvk volgen kan ook. Lezers zijn vrij te gebruiken wat ze willen en om zelf zaken toe te voegen of aan te passen, vragen te stellen of commentaar te leveren.
Gerelateerd

Van 80x86 naar ARM
Arnout van Kempen over rommelen in een digitale wereld.

Van INT 21h naar syscalls
Arnout van Kempen over rommelen in een digitale wereld.

De TSR en protected mode
Arnout van Kempen over rommelen in een digitale wereld.

IBM-pc en MS-DOS
Arnout van Kempen over rommelen in een digitale wereld.

Nogmaals ons programmaatje
Arnout van Kempen over rommelen in een digitale wereld.