RPI assembler tutorial #5

Tot nu voerden onze kleine programmaatjes de instructies na elkaar uit. Het zou kunnen reageren op bestaande voorwaarden die verschillende instructiereeksen nodig hebben. Dit is het doel van de branch instructies

Een speciaal register

In deel 2 heb ik uitgelegd dat de Raspberrypi’s ARM cpu 16 general-purpose integer registers heeft en ook dat sommige speciale rollen hebben in het programma (zoals r0). Nu wordt dit belangrijk.

Zo is register r15 speciaal omdat het ook nog een andere naam heeft: pc. Register r15 wordt dan ook enkel gebruikt met de naam pc omdat het anders te verwarrend is. r15 is wel correct, maar het brengt gewoon verwarring.

pc staat voor program counter. Dit is de naam die stamt uit het begin van de computers. Dit register wordt ook wel de ip of instructie pointer genoemd in andere architecturen zoals i386. Deze bevat het adres van de volgende instructie die zal worden uitgevoerd.

Wanneer de arm processor een instructie uitvoert kunnen er 2 dingen gebeuren aan het eind. Als de instructie de pc niet aanpast (wat voor de meeste instructies het geval is), word het pc-register met 4 opgeteld. Dit is 4 omdat arm instructies 32bit zijn.

Zodra de processor de instructie heeft uitgevoerd, gaat het de waarde in de program counter gebruiken als het adres voor de volgende instructie. Op deze manier zal als een instructie de program counter niet aanpas, gewoon de volgende instructie worden uitgevoerd. Dit is impliciet sequensen: Eens een instructie is uitgevoerd, dan wordt gewoon de volgende uitgevoerd. Maar als een instructie de program counter aanpast, als bijvoorbeeld er een andere waarde dan 4 aan wordt toegevoegd, kunnen we een andere instructie in het programma uitvoeren (een beetje zoals de GOTO-commando in batch). Dit proces wordt ook branchering genoemd. In ARM gebeurt dit met de branch instructie.

Onvoorwaardelijke branches

Je kan de processor vertellen om onvoorwaardelijk te branchen met de instructie b (wat staat voor branch). Zoals in het volgende voorbeeld:

/* -- branch01.s */
.text
.global main
main:
mov r0, #2 /* r0 ← 2 */
b end /* branch to 'end' */
mov r0, #3 /* r0 ← 3 */
end:
bx lr

Als je dit uitvoert dan zal je een error code van 2 zien.

Wat hier gebeurd is dat de instructie ‘b end’ de program counter aanpas naar de instructie met het label ‘end’ en dat is ‘bx lr’, De instructie die wordt uitgevoerd op het einde van het programma. Hierdoor is de instructie ‘mov r0, #3’ nooit uitgevoerd.

op punt is is de b instructie onvoorwaardelijk gebranched. Dit lijkt nutteloos, maar dat is niet het geval. Deze instructie is essentieel in bepaalde gevallen, zeker in combinatie met voorwaardelijke branching.

Voorwaardelijke branches

Als een processor enkel kon branchen zonder voorwaarden dan zou het niet echt handig zijn. Het is veel handiger als er bepaalde voorwaarden voldaan zijn (denk maar aan control-flow tools). Dus een processor moet bepaalde voorwaarden kunnen vergelijken en evalueren.

Voordat we verdergaan moeten we nog een ander register leren kennen: cpsr (Current Program Status Register). Dit register is een beetje speciaal en het direct wijzigingen (met mov etc.) hoort niet bij het idee van branches. Het cpsr register kan bepaalde waarden houden dat kunnen gelezen en aangepast worden tijdens het uitvoeren van een instructie. De waarden van dit register bevatten 4 voorwaarden-codes genaamd: N (negatief), Z (Zero), C (Carry) en V (Overflow). Deze 4 voorwaarden-codes zijn vaak gelezen door branch instructies. Bewerkingsinstructies en speciale test en vergelijkingsinstructies kunnen deze ook aanpassen.

De voorwaarden van deze 4 codes die de cpsr registers aanpassen zijn als volgt:

  1. N: Deze zal worden ingeschakeld als het resultaat van de instructie negatief is, ander wordt hij uitgeschakeld.
  2. Z: Deze wordt ingeschakeld als het resultaat nul (zero) is. En uitgeschakeld als het niet zo is.
  3. C: Deze wordt ingeschakeld als het resultaat een 33ste bit nodig heeft om volledig te zijn. Bijvoorbeeld een optelling die de 32 bit range van integers ‘overflow’. Er is een speciaal geval voor C en aftrekkingen waar niet-lenende aftrekkingen het inschakelen, en anders uitschakelen. Een groot getal aftrekken van een kleiner getal zet C aan, maar het zal uitgeschakeld worden als het omgekeerd is.
  4. V: Deze zal worden ingeschakeld als het resultaat van de instructie niet kan worden weergegeven in 32bits

Dus we hebben alles wat nodig is om voorwaardelijk te branchen. Laat ons starten met het vergelijken van 2 waarden. Hiervoor gebruiken we de instructie cmp.

cmp r1, r2 /* updates cpsr wanneer "r1 - r2", maar als r1 en r2 niet worden aangepast */

Deze instructie verminderd de waarde van het eerste register met de waarde van het 2e register. Voorbeelden van wat er kan gebeuren:

  • Als r2 een waarde had die groter was dan r1, dan zou N worden ingeschakeld.
  • Als r1 en r2 dezelfde waarde hebben, dan zou Z worden ingeschakeld, omdat het resultaat dan 0 is.
  • Als r1 1 was en r2 0, dan zou C worden ingeschakeld.
  • Als r1 het grootste positieve 32bit getal was (2147483647) en r2 -1 was dan zou 2147483648 niet kunnen worden weergegeven in 32 bit, dus zal V worden ingeschakeld.

Hoe kunnen deze opties nuttig zijn:

  • EQ (gelijk): Wanner Z is ingeschakeld
  • NE (niet gelijk): Wanner Z is uitgeschakeld
  • GE (Gelijk of groter dan): Met zowel V als N ingeschakeld of uitgeschakeld (V=N)
  • LT (Kleiner dan): Dit is het tegenovergesteld van GE.
  • GT (Groter dan): Dit is wanneer Z is uitgeschakeld, maar N en V beide zijn ingeschakeld.
  • LE (Kleiner of gelijk aan): Als Z is ingeschakeld, of als N en V niet beide zijn in- of uitgeschakeld.
  • MI (Minus, negatief): Als N is ingeschakeld.
  • PL (Plus, positief): Als N is uitgeschakeld.
  • VS (Overflow Set): Als V is ingeschakeld.
  • VC (Overflow clear): Als V is uitgeschakeld.
  • HI (Groter): Als C is ingeschakeld en Z is uitgeschakeld.
  • LS (Kleiner of hetzelfde): Als C is uit of Z is ingeschakeld.
  • CS/HS (Carry Set/Groter of gelijk): Als C is ingeschakeld.
  • CC/LO (CarryClear of kleiner): Als C is uitgeschakeld.

Deze instructies kunnen gecombineerd worden met onze b instructie om nieuwe instructies te genereren. Op deze manier zal ‘beq’ enkel branchen als Z 1 is. Als deze voorwaarden niet zijn voldaan, dan wordt de branch genegeerd en zal de volgende instructie worden uitgevoerd. Het is de taak van de programmeur om deze goed te gebruiken en dat de voorwaarden goed zijn voordat er gebranched wordt.

/* -- compare01.s */
.text
.global main
main:
    mov r1, #2       /* r1 ← 2 */
    mov r2, #2       /* r2 ← 2 */
    cmp r1, r2       /* update cpsr condition codes with the value of r1-r2 */
    beq case_equal   /* branch to case_equal only if Z = 1 */
case_different :
    mov r0, #2       /* r0 ← 2 */
    b end            /* branch to end */
case_equal:
    mov r0, #1       /* r0 ← 1 */
end:
    bx lr

Als je dit uitvoert zal je een error code van 1 terugkrijgen omdat zowel r1 en r2 dezelfde waarde hebben. Als je r1 naar 3 zet op lijn 5 dan zal je een 2 moeten krijgen. Let op dat bij de case_diffirent: label wel op het einde een onvoorwaardelijke branch naar het end label gaat, omdat het resultaat anders altijd 1 is.

Dit was alles voor deel 5.

 

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *