Instructiesets en assembleertaal

Leer hoe software via een instructieset met hardware communiceert. Lees en schrijf assembleertaal met RISC-V en gebruik daarbij registers, geheugenoperanden, constanten, rekenkundige en logische bewerkingen en voorwaardelijke sprongen. Vertaal expressies, keuzestructuren, lussen, pointers en tekenreeksen uit C naar instructies. Codeer instructies als 32-bitsmachinecode en decodeer ze weer, bouw procedureaanroepen met de stack en volg een programma tijdens het compileren, assembleren, linken en laden. Vergelijk ten slotte RISC-V met complexe instructiesets zoals x86 en beoordeel wat het ontwerp van een instructieset betekent voor de prestaties.

GemiddeldLes 2 van 6 in Cursus Computerarchitectuur: processors, geheugen en prestaties

Wat je leert

  1. Leg uit wat een instructieset is, lees RISC-V-assembleertaal en gebruik registers, geheugen en constanten als operanden van instructies.
    • Leg uit wat een instructiesetarchitectuur is
      Beschrijf de instructieset als de overeenkomst tussen software en hardware, het principe van het opgeslagen programma en de samenhang tussen assembleertaal, machinecode en de RISC-V-basisinstructieset.
    • Gebruik registers om expressies te berekenen
      Benoem de 32 RISC-V-integerregisters en hun gebruikelijke rollen en vertaal rekenkundige expressies naar reeksen registerinstructies.
    • Laad gegevens uit het geheugen en sla gegevens in het geheugen op
      Gebruik byteadressen, gegevensgroottes, uitlijning en bytevolgorde om woorden, halve woorden en bytes, waaronder array-elementen, tussen geheugen en registers te verplaatsen.
    • Werk met constanten en onmiddellijke operanden
      Gebruik onmiddellijke operanden voor kleine constanten en bouw volledige 32-bitsconstanten en adressen op met lui, auipc, addi en de pseudo-instructies li en la.
  2. Gebruik rekenkundige, logische, schuif- en vergelijkingsinstructies en vertaal keuzestructuren en lussen uit C naar voorwaardelijke en onvoorwaardelijke sprongen.
    • Gebruik rekenkundige, logische en schuifinstructies
      Reken met optellen, aftrekken, bitsgewijze logica, verschuivingen en vergelijken op kleiner dan, inclusief overloop, interpretatie met en zonder teken, maskers en vermenigvuldigen of delen met machten van 2.
    • Voer voorwaardelijke en onvoorwaardelijke sprongen uit
      Gebruik instructies die vergelijken en voorwaardelijk springen, voorwaarden met en zonder teken, onvoorwaardelijke sprongen en hun pseudo-instructies om te veranderen welke instructie als volgende wordt uitgevoerd.
    • Vertaal keuzestructuren en lussen uit C naar assembleertaal
      Zet if-else-instructies, samengestelde voorwaarden, while- en for-lussen en switch-instructies om in sprongen en volg de resulterende code met concrete waarden.
  3. Zet assembleerinstructies en 32-bitsmachinecode in elkaar om met de zes RISC-V-instructieformaten en leg uit waarom de formaten zo zijn ontworpen.
    • Codeer registerinstructies, instructies met onmiddellijke operanden en opslaginstructies
      Deel de velden van instructies van het R-, I- en S-type in en zet instructies zoals add, sub, addi, schuifinstructies, lw en sw om naar binaire en hexadecimale vorm.
    • Codeer voorwaardelijke sprongen, onvoorwaardelijke sprongen en onmiddellijke operanden voor de hoge bits en decodeer machinecode
      Codeer instructies van het B-, J- en U-type met PC-relatieve offsets en onmiddellijke operanden voor de hoge bits en decodeer elk 32-bits RV32I-woord terug naar assembleertaal.
  4. Volg de RISC-V-aanroepconventie om argumenten door te geven, resultaten terug te geven en registers te bewaren, gebruik stackframes voor geneste en recursieve procedures en vertaal pointers, arrays en tekenreeksen uit C.
    • Roep procedures aan en keer eruit terug volgens de aanroepconventie
      Geef argumenten en resultaten door in registers, roep procedures aan met jal en keer terug met ret, en bepaal welke registers de aanroeper of de aangeroepen procedure moet bewaren.
    • Bouw stackframes voor geneste en recursieve procedures
      Reserveer en geef stackframes vrij, sla het terugkeeradres en registers op bij geneste en recursieve aanroepen en plaats de stack in de geheugenindeling van een programma.
    • Vertaal pointers, arrays en tekenreeksen uit C naar assembleertaal
      Zet dereferenties en rekenkundige bewerkingen met pointers, structuurvelden, arrayparameters en tekenreeksen met een afsluitende nulbyte om naar laad- en opslaginstructies en adresberekeningen.
  5. Leg uit hoe een compiler, assembler, linker en loader C-broncode omzetten in een programma dat in het geheugen draait, inclusief objectbestanden, relocatie en dynamisch linken.
    • Leg uit hoe compilers en assemblers objectbestanden maken
      Beschrijf de vertaalhiërarchie, wat compilers en assemblers doen, assemblerdirectieven en symbolen, en de onderdelen van een objectbestand.
    • Leg uit hoe linkers en loaders een uitvoerbaar bestand maken en starten
      Beschrijf hoe een linker modules plaatst en adressen aanpast, vergelijk statisch en dynamisch linken en leg uit hoe een loader en systeemaanroepen een programma starten en beëindigen.
  6. Vergelijk RISC-V met complexe instructiesets zoals x86 en beoordeel afwegingen bij instructiesetontwerpen voor prestaties, codeomvang en compatibiliteit.
    • Vergelijk de ontwerpkenmerken van RISC en CISC
      Beschrijf de bepalende kenmerken van RISC- en CISC-instructiesets en vergelijk RISC-V met x86 op operanden, codering, adresseringswijzen, registers en conditiecodes.
    • Beoordeel afwegingen bij instructiesetontwerpen voor prestaties, codeomvang en compatibiliteit
      Gebruik de CPU-prestatieformule om ontwerpen te vergelijken, leg uit hoe moderne processors de grens tussen RISC en CISC vervagen en beoordeel veelvoorkomende misvattingen over instructiesets.

Instructiesets en assembleertaal: uitleg

6 hoofdstukken

1

Instructies, registers en geheugen

Een instructieset verbindt een programma met een processor. Begin met het lezen van het doel en de bronnen van een RISC-V-instructie en volg daarna waarden door registers en geheugen. Byteadressen verklaren hoe arrays en structuren worden benaderd; velden voor onmiddellijke operanden verklaren waarom sommige constanten meer dan één instructie nodig hebben. Door waarden, adressen en geheugeninhoud van elkaar te onderscheiden kun je elke assembleerregel gemakkelijker volgen.

1.1 De instructieset als softwareovereenkomst

Een processor voert instructies uit die door bits worden voorgesteld. Een instructiesetarchitectuur (ISA) geeft die bits een gedeelde betekenis, zodat software dezelfde bewerkingen op verschillende implementaties kan aanvragen.

Wat de ISA definieert

De ISA specificeert instructies, zichtbare registers, gegevensgroottes, geheugengedrag en instructiecoderingen. Een instructie zoals add a0, a1, a2 moet op elke processor die haar ondersteunt het gedefinieerde resultaat opleveren. De interne schakelingen en planning horen bij de microarchitectuur. Twee chips kunnen sterk verschillen in snelheid en energieverbruik terwijl ze dezelfde ISA implementeren.

Voor binaire compatibiliteit moet ook de softwareomgeving overeenkomen. Een programma dat een dienst van het besturingssysteem of een bibliotheek aanroept, is van meer afhankelijk dan de betekenis van instructies. Registerconventies en gegevensindelingen maken deel uit van de binaire applicatie-interface, of ABI.

Van leesbare code naar bits

Voor C kiest een compiler assembleerinstructies; een assembler codeert die als machinecode. Assembleertaal is tekst met instructienamen, registers, opmerkingen en labels. Machinecode is de binaire representatie die de processor ophaalt. Een label zoals Start: geeft een instructieadres een naam; het is geen instructie die tijdens de uitvoering wordt uitgevoerd. Een opmerking begint met #.

Start:
    add a0, a1, a2   # a0 receives a1 + a2

Het eerste register is het doel. Als a1 de waarde 14 bevat en a2 de waarde 5, schrijft de instructie 19 naar a0; de bronnen blijven ongewijzigd. Complexere expressies hebben meerdere bewerkingen nodig. Voor het optellen van vier registerwaarden zijn drie optellingen nodig, waarbij tussenresultaten in registers worden bewaard.

De meeste instructienamen komen overeen met één machine-instructie. Een pseudo-instructie is een verkorte schrijfwijze voor de assembler: mv vraagt om een registerkopie en li om een constante. Een pseudo-instructie kan worden uitgewerkt tot één of meer echte instructies, dus het tellen van assembleerregels geeft niet altijd de codeomvang.

Instructies opgeslagen in het geheugen

Volgens het principe van het opgeslagen programma worden zowel instructies als gegevens als getallen in het geheugen voorgesteld. De programmateller, pc, duidt de op te halen instructie aan. Bij een reeks 32-bitsinstructies verhoogt de normale uitvoering die met 4 bytes. Voorwaardelijke en onvoorwaardelijke sprongen kiezen een ander volgend adres. De processor voert de codering uit, niet de assembleerschrijfwijze die die codering oorspronkelijk opleverde.

RISC-V-basissets en -extensies

RISC-V is een open ISA-standaard zonder licentievergoedingen die wordt onderhouden door RISC-V International. RV32I biedt 40 basisinstructies voor gehele getallen en 32-bitsintegerregisters; RV64I gebruikt 64-bitsintegerregisters. Beide bieden 32 integerregisters. Optionele extensies voegen mogelijkheden toe: M biedt vermenigvuldiging en deling van gehele getallen, A atomaire bewerkingen, F en D drijvendekommagetallen, C gecomprimeerde instructies en V vectoren. De voorbeelden gebruiken RV32I en gebruiken M waar mul voorkomt.

RISC-V-assembleertaal is specifiek voor RISC-V. Een C-programma kan opnieuw worden gecompileerd voor x86 of ARM, maar de RISC-V-machinecode kan niet rechtstreeks als instructies voor die ISA's worden uitgevoerd. Een simulator kan RISC-V-instructies uitvoeren op een host met een andere ISA en veranderingen in registers en geheugen stap voor stap tonen.

Belangrijkste punten

  • De ISA definieert zichtbaar gedrag; de microarchitectuur implementeert het.
  • Een assembler codeert assembleertekst; pseudo-instructies kunnen worden uitgewerkt tot meerdere instructies.
  • Bij 32-bitsinstructies verhoogt opeenvolgende uitvoering de pc met 4 bytes.
  • RISC-V combineert een basis-ISA met optionele extensies.

1.2 Registers en resultaten van expressies

Registers bevatten de waarden die een instructie direct kan gebruiken. In RV32I bevat elk integerregister 32 bits en duidt een registernaam een opslagplaats aan, geen C-variabele of gegevenstype.

Registernamen en rollen

Er zijn 32 integerregisters, x0 tot en met x31. De programmateller staat daar los van. Vijf bits duiden één integerregister aan, omdat er 32 mogelijke 5-bits­patronen zijn. Een kleine registerbank beperkt ook de toegangskosten en de ruimte die registervelden in instructies innemen.

RegistersABI-namenGebruikelijk doel
x0zeroGeeft bij lezen altijd 0
x1, x2ra, spTerugkeeradres en stackpointer
x3, x4gp, tpGlobale pointer en threadpointer
x5–x7, x28–x31t0–t6Tijdelijke waarden
x8–x9, x18–x27s0–s11Waarden die bij aanroepen behouden blijven
x10–x17a0–a7Argumenten; a0–a1 geven ook resultaten terug

Register s0 kan ook dienen als fp, de framepointer. Schrijfbewerkingen naar zero worden weggegooid, zodat het register een nuloperand kan leveren zonder eerst een constante te laden. Zo kopieert add a0, a1, zero de waarde van a1.

Deze namen drukken een softwareafspraak uit. De hardware bewaart met name een s-register niet automatisch. Een procedure die het register wijzigt, moet zelf de oorspronkelijke waarde herstellen. De standaard-ABI reserveert ook gp en tp voor hun rol in de omgeving; het zijn geen gewone hulpregisters.

Splits een expressie op in bewerkingen

Vertaal f = (g + h) - (i + j); met g, h, i, j in s1–s4 en het resultaat in s0:

add t0, s1, s2
add t1, s3, s4
sub s0, t0, t1

Met de waarden 9, 4, 6 en 1 laat de eerste instructie 13 achter in t0. De tweede laat 7 achter in t1. De aftrekking schrijft 6 naar s0. Elk tijdelijk register bewaart een resultaat totdat de volgende instructie het nodig heeft.

De volgorde van de twee bronoperanden is belangrijk bij aftrekken: sub s0, t1, t0 zou −6 geven. Het doel staat altijd voorop, dus sub t0, s0, t1 zou in plaats daarvan een tijdelijk register overschrijven en de oude waarde van s0 lezen. Schrijf bij het volgen van de code na elke regel het doel op; werk niet beide bronregisters bij.

Bits krijgen betekenis door gebruik

Een register heeft geen aanduiding voor waarden met of zonder teken. Het patroon 0xFFFFFFFF kan −1 met teken of 4.294.967.295 zonder teken betekenen. Een vergelijking met teken en een vergelijking zonder teken kunnen daarom voor dezelfde opgeslagen bits verschillen. Ook adressen zijn bitpatronen in registers; een laadinstructie interpreteert haar basis als een adres.

Bewaar waarden zolang dat nodig is

Een compiler wijst veelgebruikte waarden toe aan registers. Als er meer actieve waarden nodig zijn dan de registers kunnen bevatten, slaat de compiler er enkele op in het geheugen, een proces dat registerspilling heet, en laadt ze later opnieuw. Dit kost daadwerkelijk opslag- en laadinstructies.

Aanroepen vormen een andere grens. Een aangeroepen procedure kan t0–t6 en a0–a7 overschrijven. Bewaar een waarde die na een aanroep nodig is in een correct behouden s-register of sla die vóór de aanroep op de stack op. Als je t0 kiest alleen omdat het vrij was, blijft de waarde niet vanzelf behouden.

Belangrijkste punten

  • RV32I heeft 32 integerregisters van 32 bits; de pc is afzonderlijk.
  • Schrijfbewerkingen naar zero verdwijnen en lezen geeft altijd 0.
  • Registerberekeningen schrijven naar de eerste operand en lezen de volgende operanden.
  • Tijdelijke resultaten moeten beschikbaar blijven tot ze voor het laatst worden gebruikt.
  • Registers die de aanroeper moet bewaren, kunnen door een procedureaanroep veranderen.

1.3 Verplaats waarden tussen geheugen en registers

Geheugenadressen duiden bytes aan. Laadinstructies brengen gegevens naar registers en opslaginstructies kopiëren registerbits terug naar het geheugen. In een load-storearchitectuur lezen rekenkundige instructies hun operanden niet rechtstreeks uit het geheugen.

Groottes en byteoffsets

Een byte neemt één adres in, een half woord twee en een 32-bitswoord vier. RV32 heeft een 32-bitsadresruimte van maximaal 4 GiB. In de ILP32-C-ABI gebruikt char 1 byte, short 2 en gebruiken int, long en pointers er 4.

De instructies lw en sw verplaatsen woorden. Laadinstructies voor halve woorden zijn lh en lhu, en voor bytes lb en lbu. Voor de kleinere groottes gebruiken opslaginstructies sh en sb. Een opslaginstructie kopieert de onderste bits van de bron; sb schrijft alleen de onderste 8 bits.

Een adres bestaat uit een basisregister plus een 12-bitsbyteoffset met teken:

lw t0, 32(s3)
sw t0, 48(s3)

De laadinstructie leest het woord op het adres in s3 plus 32. Ze telt geen 32 op bij de geladen waarde en verandert s3 niet. De opslaginstructie schrijft die waarde op de basis plus 48. Het offsetbereik loopt van −2048 tot en met 2047.

Vertaal een toegang tot een array

Voor een int-array met de basis in s3 begint element A[i] op de basis plus 4 keer de index. Om A[12] = h + A[8]; te implementeren, met h in s1:

lw t0, 32(s3)      # A[8]
add t0, s1, t0
sw t0, 48(s3)      # A[12]

Als A[8] gelijk is aan 80 en h aan 3, wordt A[12] gelijk aan 83. Een offset van 8 zou A[2] selecteren, omdat het adres bytes telt. Vorm voor een variabele index in s4 eerst het adres:

slli t1, s4, 2
add t1, s3, t1
lw t0, 0(t1)

De schaal volgt uit de elementgrootte, niet uit de naam van de processor. Een RV64-LP64-pointer of long neemt 8 bytes in en gebruikt ld/sd; een RV64-int neemt nog steeds 4 bytes in.

Breid een kleine geladen waarde uit

Een register moet 32 bits bevatten, ook als een laadinstructie er maar 8 leest. lb kopieert de tekenbit van de byte naar elke hogere bit; lbu vult de hogere bits met nullen. Zo wordt byte 0xF0 met lb gelijk aan 0xFFFFFFF0 (−16), maar met lbu aan 240. Hetzelfde onderscheid geldt voor lh en lhu. Bytes zonder teken, waaronder bytes van tekenreeksen, gebruiken lbu.

Lees de bytevolgorde

In little-endiangeheugen staat de minst significante byte op het laagste adres. Als je 0x12345678 opslaat op 0x1000, komen 0x78, 0x56, 0x34, 0x12 op de adressen 0x1000 tot en met 0x1003. Een bytelaadinstructie vanaf het eerste adres leest 0x78. De big-endianvolgorde zet 0x12 voorop; RISC-V-systemen kunnen beide volgordes definiëren, terwijl de gebruikelijke onderwijssystemen little-endian zijn.

Een natuurlijk uitgelijnd adres is een veelvoud van de toegangsgrootte: 4 voor een woord en 2 voor een half woord. Natuurlijk uitgelijnde toegangen worden door de architectuur ondersteund. Een niet-uitgelijnde toegang kan werken, extra tijd kosten of een uitzondering veroorzaken, afhankelijk van de uitvoeringsomgeving. Plaats woorden op grenzen van 4 bytes en ga 4 verder om het volgende woord te bereiken.

Belangrijkste punten

  • Laad- en opslagoffsets tellen bytes, geen array-elementen.
  • Bereken een adres met een variabele index voordat je de laad- of opslaginstructie uitvoert.
  • Laadinstructies met teken passen tekenuitbreiding toe; laadinstructies zonder teken passen nuluitbreiding toe.
  • Little-endiangeheugen plaatst de minst significante byte voorop.
  • Natuurlijke uitlijning volgt uit de toegangsgrootte.
  • RV64 LP64 maakt pointers en long-waarden breder, terwijl int 4 bytes blijft.

1.4 Laat constanten in instructievelden passen

Een onmiddellijke operand is een constante die in een instructie is gecodeerd. Daardoor kan een veelvoorkomende bewerking zoals het verhogen van een teller een afzonderlijke laadinstructie vermijden.

Kleine constanten

addi t0, t0, 1 telt 1 op bij t0. De onmiddellijke operand is een 12-bitsgetal met teken in tweecomplement, van −2048 tot 2047. De processor past vóór het optellen tekenuitbreiding tot 32 bits toe. Trek een kleine constante af met een negatieve onmiddellijke operand, zoals in addi t0, t0, -5; RV32I heeft geen aftrekinstructie met een onmiddellijke operand.

Laadinstructies, jalr en gewone rekenkundige en logische instructies met onmiddellijke operanden gebruiken ook 12-bits onmiddellijke operanden met teken. Met name andi, ori en xori passen geen nuluitbreiding toe op hun constanten. Schuifinstructies met een onmiddellijk aantal posities coderen daarentegen in RV32 een aantal schuifposities van 0 tot en met 31.

Nuttige verkorte schrijfwijzen voor de assembler zijn:

TekstBasisinstructie of uitwerking
mv rd, rsaddi rd, rs, 0
nopaddi x0, x0, 0
not rd, rsxori rd, rs, -1
neg rd, rssub rd, x0, rs
li rd, c, voor een kleine caddi rd, x0, c

De constante −1 wordt bij tekenuitbreiding een reeks enen, waardoor xori met −1 elke bit omkeert.

Bouw alle 32 bits op

lui plaatst een 20-bitsveld in bits 31–12 en zet de onderste 12 bits op nul. Voor 0x12345678:

lui t0, 0x12345
addi t0, t0, 0x678

De tussenwaarde is 0x12345000; door 1.656 op te tellen ontstaat het lage deel. Een laag deel waarvan bit 11 is gezet vraagt meer aandacht. Het 12-bitspatroon stelt een negatieve term voor, dus verhoog het hoge deel voordat je het lage deel optelt.

Voor 0x00001800 betekent het lage patroon 0x800 de waarde −2048. Gebruik:

lui t0, 0x2
addi t0, t0, -2048

Het eerste resultaat is 0x2000. Als je 0x800 aftrekt, krijg je 0x1800. Met het hoge veld 0x1 zou je 0x800 krijgen, één blok van 4096 te laag.

Lees voor elke 32-bitsconstante de onderste 12 bits als een waarde met teken. Trek die waarde met teken af van de gewenste constante, deel door 4096 en behoud de onderste 20 bits voor lui. Voor 0xDEADBEEF is het lage deel met teken −273, dus gebruik het hoge veld 0xDEADC en daarna addi met −273. Assemblers voeren deze opsplitsing uit voor li.

Constanten, adressen en inhoud

li t0, 100 zet het getal 100 in een register. lw t0, 100(zero) leest de inhoud van het geheugen op adres 100. Een adres kan zelf als waarde worden geladen: la t0, Item vraagt de assembler om het adres met de naam Item.

auipc telt een onmiddellijke operand voor de hoge bits, 12 bits naar links verschoven, op bij het adres van die instructie. In combinatie met een bewerking met een onmiddellijke operand of een laadinstructie biedt dit PC-relatieve toegang over ongeveer ±2 GiB. Een lokale la gebruikt gewoonlijk auipc plus addi; andere uitwerkingen ondersteunen toegang tot gedeelde bibliotheken.

Een constante zoals 5.000 kan geen operand van addi zijn. Laad die met li in een tweede register en gebruik daarna add. Een grotere waarde krijgt geen bredere onmiddellijke operand alleen omdat die hexadecimaal is geschreven.

Belangrijkste punten

  • Een 12-bits onmiddellijke operand met teken loopt van −2048 tot 2047.
  • lui levert de bovenste 20 bits met 12 nullen eronder.
  • Als het lage 12-bitsdeel negatief is, compenseer je dat in het hoge veld.
  • li laadt een waarde, la vormt een adres en lw leest het geheugen.
  • auipc bouwt een adres op relatief aan zijn eigen pc.
2

Rekenkundige bewerkingen en programmaverloop

Zodra waarden in registers staan, berekenen instructies resultaten en bepalen ze wat daarna wordt uitgevoerd. Bitsgewijze logica en verschuivingen werken op representaties; vergelijkingen met en zonder teken geven die patronen verschillende betekenissen. Voorwaardelijke en onvoorwaardelijke sprongen zetten keuzestructuren en lussen uit C om in expliciete paden. Een pad volgen betekent zowel de huidige waarden als het adres van de volgende instructie bijhouden, inclusief de laatste mislukte lustest.

2.1 Reken met bits en waarden met teken

Een rekenkundige instructie leest registerbits en schrijft een nieuw bitpatroon. Om dat patroon te begrijpen moet je zowel de bewerking als de bedoelde interpretatie met of zonder teken kennen.

Optellen, aftrekken en overloop

In RV32I behouden add, sub en addi de onderste 32 bits van het resultaat. Als je 1 optelt bij 0x7FFFFFFF, krijg je 0x80000000, dat als geheel getal met teken −2.147.483.648 voorstelt. De instructie veroorzaakt geen overloopuitzondering.

Bij optellen zonder teken kan software terugwikkeling detecteren:

add t0, a0, a1
bltu t0, a0, Overflow

Een teruggewikkelde som zonder teken is kleiner dan een invoerwaarde. Deze test zonder teken is niet de regel voor overloop met teken, waarbij de tekens van de invoerwaarden en het resultaat van belang zijn.

De M-extensie biedt mul, mulh, mulhu, div, divu, rem en remu. mul behoudt de onderste productbits; de instructies voor de bovenste productbits maken onderscheid tussen invoer met en zonder teken. Deling door nul geeft een quotiënt dat uit enen bestaat en het oorspronkelijke deeltal als rest, zonder een delingsuitzondering.

Bits selecteren, aanzetten en omkeren

and, or en xor werken onafhankelijk op elke bit. EN met 1 behoudt een bit; EN met 0 zet de bit op nul. OF met 1 zet een bit aan en exclusief OF met 1 keert de bit om.

srli t0, a0, 4
andi t0, t0, 0xF

Met a0 = 0xABCD geeft verschuiven naar rechts 0xABC. Maskeren met 0xF laat 0xC over, de oorspronkelijke bits 7–4. De verschuiving zet het veld onderaan; het masker verwijdert alles erboven. Gebruik ori a0, a0, 0x10 om bit 4 aan te zetten zonder de andere te veranderen. xori met −1 keert alle 32 bits om.

Kies een verschuiving op basis van de aanvulregel

Een verschuiving naar links, sll of slli, vult lage posities met nullen. Een logische verschuiving naar rechts, srl of srli, vult hoge posities met nullen. Een rekenkundige verschuiving naar rechts, sra of srai, kopieert de tekenbit naar hoge posities. Schuifinstructies met een register gebruiken in RV32 alleen de onderste 5 bits van het register met het aantal schuifposities.

Als je 13 met 3 posities naar links schuift, krijg je 104. Dit vermenigvuldigt met 8 omdat geen benodigde bits verloren gaan. Bij verschuivingen naar links die significante bits verwijderen, blijft alleen het resultaat behouden dat in het register past.

Voor waarden zonder teken deelt een verschuiving naar rechts door een macht van 2 en rondt naar beneden af. Voor waarden met teken rondt een rekenkundige verschuiving naar rechts af naar min oneindig: als je −7 met 1 positie naar rechts schuift, krijg je −4. Gehele deling met teken in C rondt af naar nul en geeft −3 voor -7 / 2. Een compiler moet een correctie toepassen voordat hij die deling met teken door een verschuiving vervangt. Een logische verschuiving van hetzelfde negatieve patroon vult aan met nullen en geeft een groot positief resultaat.

Vergelijk om een waarde te produceren

slt schrijft 1 als de eerste bron, geïnterpreteerd met teken, kleiner is en anders 0. sltu gebruikt interpretatie zonder teken. Met 0xFFFFFFFF en 1 als invoer schrijft slt 1, terwijl sltu 0 schrijft. De versies met een onmiddellijke operand zijn slti en sltiu; de laatste past nog steeds tekenuitbreiding toe op de onmiddellijke operand vóór de vergelijking zonder teken.

Bitsgewijze EN verschilt van && in C. Een bitsgewijs resultaat kan veel aangezette bits bevatten; logische EN levert 0 of 1 op en kan de evaluatie van de tweede expressie overslaan.

Belangrijkste punten

  • Rekenkundige RV32-instructies behouden de onderste 32 bits en veroorzaken geen uitzondering bij overloop.
  • EN selecteert bits, OF zet ze aan en exclusief OF keert ze om.
  • Rekenkundig naar rechts schuiven behoudt het teken; logisch naar rechts schuiven vult aan met nullen.
  • srai en deling met teken in C ronden negatieve getallen die geen veelvoud van de deler zijn verschillend af.
  • slt en sltu interpreteren dezelfde registerpatronen verschillend.
  • Vermenigvuldiging en deling uit de M-extensie staan los van RV32I.

2.2 Kies de volgende instructie

Een voorwaardelijke sprong vergelijkt twee registers en verandert de programmateller als de test slaagt. Als de test mislukt, gaat de uitvoering verder met de volgende instructie.

Kies de vergelijking
InstructieSprongvoorwaarde
beq, bneGelijk, ongelijk
blt, bgeKleiner dan met teken, groter dan of gelijk met teken
bltu, bgeuKleiner dan zonder teken, groter dan of gelijk zonder teken

RISC-V combineert vergelijking en sprong in één instructie. Het heeft geen conditievlaggen voor gehele getallen die eerst moeten worden ingesteld. Gebruik zero als een van de bronnen om met nul te vergelijken. beqz a0, L wordt uitgewerkt tot beq a0, zero, L.

De vormen voor groter dan en kleiner dan of gelijk zijn verkorte schrijfwijzen voor de assembler met verwisselde bronnen. bgt a0, a1, L wordt uitgewerkt tot blt a1, a0, L. Het omwisselen van de bronvolgorde is net zo belangrijk als het kiezen van de juiste instructienaam.

Programmaverloop met en zonder teken

Als a0 de waarde 0xFFFFFFFF bevat en a1 de waarde 1, neemt blt a0, a1, L de sprong omdat −1 met teken kleiner is dan 1. bltu doet dat niet, omdat het eerste patroon een grote waarde zonder teken is. Gebruik vergelijkingen die passen bij de betekenis van de gegevens, waaronder een vergelijking zonder teken voor adressen.

Een bereikcontrole zonder teken kan zowel negatieve indexen met teken als indexen op of boven een kleine niet-negatieve lengte afwijzen:

bgeu a0, a1, OutOfBounds

Bij een lengte van 20 verschijnt een index van −1 als 4.294.967.295 zonder teken en wordt de sprong genomen. Bij een index van 19 gebeurt dat niet; bij een index van 20 wel. De lengte moet de werkelijke niet-negatieve lengte van de array voorstellen.

Sprongen en terugkeeradressen

jal ra, f schrijft het adres van de volgende instructie, pc + 4, naar ra en springt dan naar f. Een gewone j L wordt uitgewerkt tot jal zero, L; het opgeslagen terugkeeradres wordt weggegooid. Dit is geschikt voor een terugsprong in een lus, maar legt geen terugkeeradres vast voor een aanroep.

jalr rd, offset(rs1) telt een 12-bitsoffset met teken op bij een registerwaarde, zet bit 0 van het doel op nul en schrijft zijn eigen pc + 4 naar rd. ret wordt uitgewerkt tot jalr zero, 0(ra) en keert terug naar het opgeslagen adres zonder een nieuw terugkeeradres te bewaren.

Voorwaardelijke sprongen en jal slaan offsets op ten opzichte van hun eigen instructieadressen. Hun bereik is respectievelijk ongeveer ±4 KiB en ±1 MiB. jalr gebruikt een doel op basis van een register en kan de hele adresruimte bereiken. Voor een verre voorwaardelijke sprong kan de test worden omgekeerd om een langere sprong over te slaan:

bne a0, a1, Skip
jal zero, Far
Skip:

Hier ligt Far binnen het bereik van jal. Als de waarden gelijk zijn, loopt de uitvoering na de korte voorwaardelijke sprong door naar de onvoorwaardelijke sprong; anders slaat ze die over.

Herken een basisblok

Een basisblok heeft één ingang aan het begin en geen overdracht van de besturing vóór het einde. Binnen het blok worden instructies na elkaar uitgevoerd. Sprongdoelen beginnen nieuwe blokken; voorwaardelijke en onvoorwaardelijke sprongen beëindigen ze. Zo kun je beredeneren welke toewijzingen altijd vóór een bepaalde test of uitgang worden uitgevoerd.

Belangrijkste punten

  • Kies voorwaardelijke sprongen met of zonder teken passend bij de betekenis van de operanden.
  • RISC-V-sprongen vergelijken registers rechtstreeks zonder conditievlaggen.
  • jal bewaart een terugkeeradres; j gooit het weg.
  • jalr gebruikt een register plus een onmiddellijke operand en zet doelbit 0 op nul.
  • Een offset van een voorwaardelijke sprong is relatief aan de spronginstructie zelf.

2.3 Zet programmaverloop uit C om in sprongen

Assembleertaal maakt elk pad door een keuzestructuur of lus expliciet. Labels markeren bestemmingen, voorwaardelijke sprongen kiezen paden en gewone sprongen voorkomen ongewenst doorlopen.

Een if-else heeft een uitgang nodig

Vertaal if (i == j) f = g + h; else f = g - h;, met f, g, h, i, j in s0–s4:

bne s3, s4, Else
add s0, s1, s2
j Exit
Else:
sub s0, s1, s2
Exit:

De eerste voorwaardelijke sprong test het tegenovergestelde van de C-voorwaarde, zodat de uitvoering bij gelijkheid doorloopt naar de optelling. De sprong na de optelling voorkomt dat de aftrekking wordt uitgevoerd. Met g = 10 en h = 3 geven gelijke indexen 13; ongelijke indexen geven 7. Als je j Exit verwijdert, wordt ook na de optelling de aftrekking uitgevoerd.

Spring voor a < b && b < c naar het else-deel zodra een van de vergelijkingen mislukt. Spring voor a < b || b < c naar het then-deel zodra een van beide slaagt. Dit implementeert kortsluitevaluatie, waarbij ook een tweede expressie wordt overgeslagen waarvan de waarde niet nodig is.

Een while-lus test vóór de lusinhoud

Bewaar voor while (save[i] == k) i++; de waarde van i in s3, die van k in s5 en de arraybasis in s6:

Loop:
slli t1, s3, 2
add t1, t1, s6
lw t0, 0(t1)
bne t0, s5, Exit
addi s3, s3, 1
j Loop
Exit:

Bij elke doorgang vormt de code het adres van het huidige element en laadt en controleert ze dat element. Met de array {7, 7, 7, 2, 7}, beginindex 0 en k = 7 eindigt de lus met index 3. De mislukte test verhoogt de index niet. De array moet binnen het geldige bereik een element bevatten dat de lus stopt.

Tel een array met een gegeven lengte op

Voor een niet-negatief aantal elementen in a1 en de arraybasis in a0:

li t1, 0
li t0, 0
Loop:
bge t0, a1, Done
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
j Loop
Done:
mv a0, t1
ret

De test bovenaan handelt een lengte van nul af zonder iets te laden. Met vier elementen {5, -2, 10, 7} is de oplopende som 5, 3, 13 en dan 20. De reeks voert 33 instructies uit: 2 initialisatie-instructies, 7 voor elk van vier geslaagde iteraties, één mislukte test en 2 afsluitende instructies.

Een versie met pointers vormt één keer een eindadres en verhoogt het huidige adres met 4. Voor een geldig arraybereik waarvan het eindadres niet terugwikkelt, kan die versie adresvergelijkingen zonder teken gebruiken:

li t1, 0
slli t0, a1, 2
add t0, a0, t0
bgeu a0, t0, Done
Loop:
lw t3, 0(a0)
add t1, t1, t3
addi a0, a0, 4
bltu a0, t0, Loop
Done:
mv a0, t1
ret

Er zijn 4 initialisatie-instructies, 4 per iteratie en 2 afsluitende instructies, samen 22 voor vier elementen. De eerste voorwaardelijke sprong slaat de lusinhoud over bij een leeg bereik; de pointer schuift na elke laadinstructie één element op.

Keuzes met meerdere mogelijkheden

Een switch kan herhaalde vergelijkingen gebruiken of een sprongtabel wanneer de case-waarden dicht bij elkaar liggen. Een keuze via een tabel controleert het case-bereik, vermenigvuldigt een index met de itemgrootte van 4 bytes, laadt het case-adres en springt ernaartoe met jalr zero, 0(t0). De bereikcontrole moet vóór het laden uit de tabel plaatsvinden, zodat een ongeldige case geen willekeurig ander adres kan selecteren.

Belangrijkste punten

  • Om het then-deel over te slaan gebruik je bij de voorwaardelijke sprong meestal de tegengestelde voorwaarde.
  • Spring na het then-deel voorbij het else-deel.
  • Samengestelde voorwaarden moeten kortsluitevaluatie behouden.
  • Controleer een getelde lus voordat je het eerste element leest.
  • Lussen met pointers kunnen herhaald schalen van indexen vermijden.
  • Een sprongtabel vereist een bereikcontrole voordat het doel wordt gekozen.
3

Instructievelden en machinecode

Assembleernamen worden vaste velden in machine-instructies. Ga van de bewerking naar het formaat en wijs vervolgens registers en onmiddellijke operanden toe aan hun bitposities. Laadinstructies, opslaginstructies en voorwaardelijke en onvoorwaardelijke sprongen gebruiken verschillende indelingen omdat ze verschillende informatie bevatten. Door in beide richtingen te coderen en decoderen ontdek je fouten in offsets, tekens en veldvolgorde die leesbare assembleertaal kan verhullen.

3.1 Codeer registerbewerkingen, laad- en opslaginstructies

Een machine-instructie is een 32-bitswoord dat in velden is verdeeld. Coderen betekent de juiste indeling kiezen, elke operand aan een veld toewijzen en elk veld op volledige breedte schrijven.

Vaste posities vereenvoudigen het decoderen

De onderste 7 bits, 6–0, bevatten de opcode. Waar aanwezig staat rd in bits 11–7, funct3 in bits 14–12, rs1 in bits 19–15 en rs2 in bits 24–20. Registervelden van vijf bits duiden x0 tot en met x31 aan. Functievelden onderscheiden bewerkingen die een opcode delen.

RV32I gebruikt de formaten R, I, S, B, U en J. Ze passen verschillende operanden in dezelfde 32 bits terwijl de registerposities gelijk blijven. Bij onmiddellijke operanden met teken bevat bit 31 het teken.

R-type: twee bronnen en een doel

Van bit 31 tot en met bit 0 is de R-indeling:

funct7 | rs2 | rs1 | funct3 | rd | opcode
   7      5     5       3     5       7 bits

Voor add s0, a0, a1 geldt rd = 8, rs1 = 10, rs2 = 11. ADD gebruikt opcode 0110011, funct3 = 000, funct7 = 0000000:

0000000 | 01011 | 01010 | 000 | 01000 | 0110011
0000 0000 1011 0101 0000 0100 0011 0011

Het hexadecimale woord is 0x00B50433. SUB verandert alleen funct7 in 0100000, wat 0x40B50433 geeft voor sub s0, a0, a1. Bronvelden staan in een andere volgorde dan de assembleeroperanden, dus plak de getoonde registernamen niet zomaar aan elkaar.

I-type: één bron en een onmiddellijke operand

De I-indeling is imm[11:0] | rs1 | funct3 | rd | opcode. addi gebruikt opcode 0010011 en funct3 = 000. In addi sp, sp, -16 bevatten beide registervelden 00010. De 12-bits onmiddellijke operand is 111111110000 (0xFF0), wat 0xFF010113 oplevert.

Een laadinstructie gebruikt ook het I-type, met de adresbasis als rs1 en het register waarin wordt geladen als rd. lw t0, 32(s3) heeft de onmiddellijke operand 32, basis x19, doel x5, opcode 0000011 en funct3 = 010, wat 0x0209A283 geeft.

Schuifinstructies met een onmiddellijk aantal posities gebruiken de onderste 5 bits van het veld voor de onmiddellijke operand als aantal schuifposities. De bovenste besturingsbits onderscheiden de verschuivingen: srli t0, t1, 3 is 0x00335293, terwijl srai t0, t1, 3 0x40335293 is. Bit 30 selecteert de rekenkundige aanvulregel.

S-type: sla een bronwaarde op

Een opslaginstructie heeft geen doelregister. De S-indeling gebruikt die ruimte voor een deel van de onmiddellijke operand:

imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode

Voor sw t0, 48(s3) levert rs2 = 5 de op te slaan waarde en rs1 = 19 de basis. De 12-bits onmiddellijke operand 48 wordt opgesplitst in 0000001 en 10000. Met funct3 = 010 en opcode 0100011 is het resultaat 0x0259A823. Er is geen rd om met t0 te vullen.

Groepeer na het samenvoegen van de velden de volledige 32-bitsreeks in acht nibbles van 4 bits en zet elk daarvan om in één hexadecimaal cijfer. Decodeer daarna het resultaat: controleer de opcode, registernummers en onmiddellijke operand met teken. Een registerveld aanvullen tot 5 bits is essentieel; als je een voorloopnul weglaat, veranderen de posities van latere velden.

Belangrijkste punten

  • Bepaal het formaat vanuit de bewerking voordat je operandvelden toewijst.
  • Registervelden zijn 5 bits breed en staan op vaste posities.
  • Het R-type gebruikt funct7 om add van sub te onderscheiden.
  • Het I-type houdt een 12-bits onmiddellijke operand bij elkaar.
  • Het S-type splitst de offset en heeft rs2 maar geen rd.
  • Controleer een gecodeerd woord door het terug te decoderen naar operanden.

3.2 Stel offsets weer samen en decodeer machinewoorden

Coderingen van voorwaardelijke en onvoorwaardelijke sprongen besparen ruimte door een offsetbit weg te laten die altijd nul is. Hun velden stellen nog steeds afstanden in bytes voor en het teken moet worden gereconstrueerd voordat het doel wordt berekend.

Sprongoffsets van het B-type

Bereken de offset als het doeladres min het adres van de spronginstructie zelf. Een bne op 0x1010 met 0x1000 als doel gebruikt −16 bytes, niet −20 en ook niet −4 instructies.

Het B-type gebruikt opcode 1100011. De offset met teken heeft 13 bits, inclusief een niet-opgeslagen nulbit 0. Het bereik loopt van −4096 tot en met +4094 bytes, in stappen van 2. De plaatsing is:

OffsetbitsInstructiebits
imm[12]31
imm[10:5]30–25
imm[4:1]11–8
imm[11]7

De bronvelden blijven op hun vertrouwde posities. De funct3-waarden zijn 000 voor beq, 001 voor bne, 100 voor blt, 101 voor bge, 110 voor bltu en 111 voor bgeu.

Voor bne t0, t1, -16 is de 13-bitsoffset 1 1111 1111 0000. De verspreide delen zijn 1, 111111, 1000 en 1 in de volgorde van de tabel. Met rs1 = 5 en rs2 = 6 is de instructie 0xFE6298E3. Als je de offset vervangt door +12, krijg je 0x00629663.

Offsets ondersteunen stappen van 2 bytes omdat gecomprimeerde instructies 2 bytes lang kunnen zijn. Een doel waarheen wordt gesprongen moet nog steeds voldoen aan de actieve instructie-uitlijning: RV32I zonder gecomprimeerde instructies vereist uitlijning op 4 bytes.

J-type en U-type

jal gebruikt opcode 1101111. De 21-bitsbyteoffset met teken laat bit 0 weg en plaatst bits in de volgorde imm[20|10:1|11|19:12] over instructiebits 31–12. Het bereik loopt van −1.048.576 tot en met +1.048.574 bytes. jal ra, 192 wordt 0x0C0000EF.

Het U-type plaatst zijn bovenste 20-bitswaarde rechtstreeks in instructiebits 31–12, gevolgd door rd en de opcode. lui gebruikt 0110111; auipc gebruikt 0010111. Dus lui t0, 0x12345 is 0x123452B7.

Decodeer eerst aan de hand van de opcode

Voor 0x00A12423 zijn de onderste 7 bits 0100011: een opslaginstructie. funct3 = 010 selecteert sw. De bronvelden geven basis x2 (sp) en waarde x10 (a0). De twee delen van de onmiddellijke operand vormen samen 8, dus de assembleerinstructie is sw a0, 8(sp).

Voeg bij een voorwaardelijke sprong de verspreide delen samen in de volgorde van de offsetbits, voeg de impliciete nul toe en pas tekenuitbreiding toe op het volledige 13-bitsresultaat. Als je de 12 opgeslagen bits behandelt als een gewone onmiddellijke operand van het I-type, gaan zowel de juiste indeling als de schaal verloren. De vaste positie van de tekenbit en het hergebruik van andere veldposities verminderen het werk van de decoder, hoewel de weergegeven indeling met de hand minder gemakkelijk te lezen is.

Relocatie behoudt onderlinge afstanden

Als een voorwaardelijke sprong en het doel ervan beide over dezelfde afstand worden verplaatst, geeft het aftrekken van hun nieuwe adressen de oude offset. Een verwijzing naar een andere sectie kan veranderen wanneer secties afzonderlijk worden ingedeeld. Een linker kan daarom een interne relatieve sprong behouden terwijl hij een verwijzing naar een extern symbool aanpast.

Belangrijkste punten

  • Offsets van voorwaardelijke sprongen en jal tellen bytes vanaf het instructieadres.
  • Het B-type en J-type laten bit 0 weg, die nul is.
  • Stel een offset opnieuw samen voordat je het teken interpreteert.
  • De opcode bepaalt hoe de overige bits worden verdeeld.
  • Als een instructie en haar doel samen worden verplaatst, blijft hun relatieve afstand gelijk.
4

Procedureaanroepen en opslag op de stack

Een aanroep moet argumenten doorgeven, nuttige toestand bewaren en naar de juiste plaats terugkeren. Registerconventies laten afzonderlijke procedures samenwerken; stackframes houden de toestand van geneste en recursieve aanroepen gescheiden. Dezelfde adresberekeningen die voor geheugenoperanden worden gebruikt, implementeren ook pointers, structuurvelden en tekenreeksen. Volg een waarde tijdens een aanroep even zorgvuldig als tijdens een rekenkundige instructie.

4.1 Geef argumenten door en bewaar registerwaarden

Een procedureaanroep draagt besturing en gegevens over tussen onafhankelijk geschreven stukken code. De aanroepconventie vertelt beide kanten waar argumenten en resultaten komen en welke registerwaarden behouden moeten blijven.

De aanroep- en terugkeerreeks

De aanroeper plaatst de eerste acht argumenten met gehele getallen in a0–a7; extra argumenten gaan op de stack. De procedure geeft een geheel getal terug in a0 en gebruikt in RV32 ook a1 voor een 64-bitsresultaat.

jal ra, f slaat het eigen adres plus 4 op in ra en springt dan naar f. jal f is de korte schrijfwijze. De pseudo-instructie call f wordt normaal uitgewerkt tot auipc plus jalr voor een groter bereik; de linker kan die reeks inkorten tot één jal. In beide gevallen volgt het terugkeeradres op de daadwerkelijke aanroepreeks.

ret is jalr zero, 0(ra). Die instructie springt via het opgeslagen adres en gooit een nieuw terugkeeradres weg. Een gewone j f gooit ook het terugkeeradres weg en zet dus geen gewone aanroep met terugkeer op.

Bepaal wie een waarde opslaat

Tot de door de aanroeper te bewaren registers behoren t0–t6, a0–a7 en ra. De aangeroepen procedure mag ze veranderen. Als de aanroeper een van hun huidige waarden later nodig heeft, slaat de aanroeper die waarde vóór de aanroep op.

Tot de door de aangeroepen procedure te bewaren registers behoren s0–s11 en sp. Een aangeroepen procedure die een s-register verandert, moet de binnengekomen waarde opslaan en vóór terugkeer herstellen. Door sp te herstellen keert de aanroeper terug naar het eigen frame. De processor dwingt deze afspraken niet af; gecompileerde modules werken samen omdat hun software de ABI volgt.

Stel dat een aanroeper een lusteller in t0 heeft en een hulpprocedure aanroept. De hulpprocedure mag t0 voor haar eigen berekening gebruiken. Door de teller naar s1 te verplaatsen kan die aanroepen van hulpprocedures overleven, maar de omringende procedure moet dan de waarde van s1 bewaren die ze van haar eigen aanroeper ontving.

Een bladprocedure heeft soms geen frame nodig

Een bladprocedure roept geen andere procedure aan. Deze functie berekent (g + h) - (i + j) uit argumenten in a0–a3:

leaf:
add t0, a0, a1
add t1, a2, a3
sub a0, t0, t1
ret

Ze gebruikt alleen werkregisters die de aanroeper moet bewaren en laat ra ongewijzigd. Voor invoer 9, 4, 6 en 1 geeft ze 6 terug. Ze heeft geen stackframe nodig.

Als de berekening in plaats daarvan s0 gebruikt, moet de procedure dat register bewaren:

addi sp, sp, -16
sw s0, 12(sp)
add t0, a0, a1
add t1, a2, a3
sub s0, t0, t1
mv a0, s0
lw s0, 12(sp)
addi sp, sp, 16
ret

Het resultaat wordt naar a0 verplaatst voordat de oude waarde van s0 wordt hersteld. Als de binnengekomen waarde van s0 1234 was, is die daarna nog steeds 1234. Het gereserveerde frame is 16 bytes groot om de standaarduitlijning van de stack te behouden, hoewel het opgeslagen woord maar 4 bytes gebruikt.

Je kunt een aanroep lezen als zes handelingen: argumenten plaatsen, de besturing overdragen, opslagruimte verkrijgen, de taak uitvoeren, het resultaat plaatsen en terugkeren. Onderscheid bij elke overgang de teruggegeven waarde van de oudere waarden die de procedure volgens de conventie moet bewaren.

Belangrijkste punten

  • Argumenten met gehele getallen beginnen in a0–a7 en het eerste resultaat staat in a0.
  • ra wijst naar de plaats na de daadwerkelijke aanroepinstructie of uitgewerkte aanroepreeks.
  • Aanroepers beschermen benodigde waarden in registers die zij moeten bewaren; aangeroepen procedures herstellen de te bewaren registers die zij wijzigen.
  • Een bladprocedure die alleen registers gebruikt die de aanroeper moet bewaren, heeft mogelijk geen frame nodig.
  • Verplaats het resultaat voordat je een bewaard register herstelt dat het resultaat bevatte.

4.2 Geef elke aanroep een eigen stackframe

Geneste aanroepen hebben een plaats nodig om de gegevens van de buitenste aanroep te bewaren. De stack biedt opslag volgens laatst erin, eerst eruit: de meest recente aanroep eindigt het eerst en geeft zijn eigen frame vrij.

Reserveer ruimte onder de oude stackpointer

De stack groeit naar lagere adressen. addi sp, sp, -16 reserveert 16 bytes; addi sp, sp, 16 geeft die vrij. De standaard-ABI houdt sp tijdens de hele uitvoering op een veelvoud van 16. Een frame kan opgeslagen registers, het terugkeeradres, naar het geheugen verplaatste waarden, lokale objecten en ruimte voor uitgaande stackargumenten bevatten.

Een gebruikelijk patroon voor een procedure die andere procedures aanroept is:

addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
# body, including calls
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret

De offsets verwijzen naar de nieuwe sp. Als die 0x2000 is, neemt de opgeslagen ra 0x200C tot en met 0x200F in. De bijbehorende epiloog herstelt waarden terwijl het frame nog bestaat en geeft het daarna vrij. Elk terugkeerpad moet de reservering ongedaan maken.

Een framepointer, volgens conventie s0, kan lokale objecten vaste offsets geven wanneer sp binnen een procedure verandert. De binnengekomen waarde moet worden bewaard, net als bij elk ander gebruik van s0.

Recursie bewaart afzonderlijke kopieën

De faculteitsprocedure heeft de oorspronkelijke n nodig na het berekenen van fact(n - 1). Ze heeft ook haar eigen terugkeeradres nodig nadat de recursieve jal de waarde van ra overschrijft:

fact:
addi sp, sp, -16
sw ra, 12(sp)
sw a0, 8(sp)
li t0, 2
blt a0, t0, Base
addi a0, a0, -1
jal ra, fact
lw t1, 8(sp)
mul a0, t1, a0
j Done
Base:
li a0, 1
Done:
lw ra, 12(sp)
addi sp, sp, 16
ret

De mul uit de M-extensie vermenigvuldigt het teruggegeven binnenste resultaat met de oorspronkelijke n. Voor fact(3) bestaan frames voor 3, 2 en 1 naast elkaar. De basisaanroep geeft 1 terug; de volgende geeft 2 terug; de buitenste aanroep geeft 6 terug. Met de beginwaarde sp = 0x7FFFFF00 verlagen drie frames die met 48 bytes tot 0x7FFFFED0. Bij terugkeer worden ze in omgekeerde volgorde vrijgegeven.

De basistest handelt de niet-negatieve waarden 0 en 1 af. Voor dit 32-bitsresultaat is de faculteit exact tot en met 12!: 479.001.600. Bij 13!, dat gelijk is aan 6.227.020.800, stellen de onderste productbits niet langer het volledige resultaat voor.

Plaats de stack in het programmageheugen

Een gebruikelijke indeling heeft code in het tekstsegment, globale variabelen in statische gegevens, dynamisch gereserveerde objecten in de heap en aanroepframes op de stack. De heap groeit vaak omhoog en de stack omlaag. De daadwerkelijke plaatsing wordt bepaald door de uitvoeringsomgeving.

Een pointer naar een lokaal stackobject wordt ongeldig wanneer de levensduur van dat object bij terugkeer eindigt. Het herstellen van sp wist de oude bytes niet, maar latere aanroepen kunnen ze opnieuw gebruiken. Als je sp verandert en oude opslagoffsets blijft gebruiken, kun je op dezelfde manier de bytes van het verkeerde frame lezen.

Belangrijkste punten

  • Trek af van sp om ruimte te reserveren; tel hetzelfde bedrag op om die vrij te geven.
  • De standaard-ABI vereist stackuitlijning op 16 bytes.
  • Een geneste aanroep overschrijft ra, dus bewaar het buitenste terugkeeradres.
  • Elke recursieve activering heeft afzonderlijke opgeslagen argumenten en terugkeergegevens.
  • Alle uitgangspaden moeten het frame en de vereiste registers herstellen.
  • Een lokaal object leeft niet langer dan zijn stackframe.

4.3 Gebruik adressen voor arrays, structuren en tekenreeksen

Een pointer is een adreswaarde. Assembleertaal implementeert een dereferentie door dat adres als basis voor een laad- of opslaginstructie te gebruiken en implementeert pointerberekeningen door een afstand in bytes op te tellen.

Volg de pointer en benader dan de waarde

Met int *p in a0 leest lw t0, 0(a0) de waarde van *p, terwijl sw t0, 0(a0) die schrijft. Het register bevat het adres; het geheugen op dat adres bevat het gehele getal. Als een pointervariabele zelf in het geheugen staat, laad je eerst die pointerwaarde en gebruik je die daarna als basis voor een tweede laadinstructie.

Het adres nemen van een lokaal C-object vereist adresseerbare opslag, normaal gesproken een plaats op de stack. Een hardwareregister heeft geen geheugenadres dat als &x kan worden doorgegeven.

Pointerberekeningen in C schalen automatisch. Voor RV32 ILP32 telt het verhogen van int *p 4 bytes op, terwijl het verhogen van char *p er 1 optelt. Assembleertaal heeft geen pointertype dat de schaal bepaalt:

addi a0, a0, 4    # next int
addi a1, a1, 1    # next char

Een arrayargument is het basisadres, geen kopie van de elementen. Geef het aantal elementen afzonderlijk door wanneer een procedure dat nodig heeft; de pointer alleen bevat de arraylengte niet.

Structuurvelden hebben byteoffsets

Voor struct item { int id; int score; }; neemt id bytes 0–3 in en score bytes 4–7. Met het adres van het object in a0 leest lw t0, 4(a0) de score. In het algemeen volgen veldoffsets de regels voor indeling en opvulling van de ABI. Leid niet elke veldoffset alleen af door declaraties in de broncode te tellen.

Voor een array van deze structuren van 8 bytes begint het volgende element 8 bytes later. Binnen elk element blijft de score 4 bytes vanaf het begin van dat element staan. Elementstapgrootte en veldoffset zijn afzonderlijke onderdelen van de adresberekening.

Kopieer een tekenreeks met een afsluitende nulbyte

Een bytetekenreeks in C eindigt met een nulbyte. Om die te kopiëren lees en bewaar je één byte voordat je bepaalt of het kopiëren klaar is:

Loop:
lbu t0, 0(a1)
sb t0, 0(a0)
addi a0, a0, 1
addi a1, a1, 1
bnez t0, Loop
ret

Hier is a1 de bron en a0 het doel. Het doel moet genoeg ruimte hebben. lbu leest een byte zonder teken; sb slaat alleen de onderste 8 bits daarvan op. Beide pointers schuiven met 1 op, ook in de laatste iteratie. De voorwaardelijke sprong test de gekopieerde byte, dus de afsluitende nul wordt ook opgeslagen.

Het kopiëren van “RISC” kost vijf iteraties voor vier letters en de nulbyte. Met vijf instructies per iteratie plus ret worden 26 instructies uitgevoerd. Een lege tekenreeks heeft nog steeds één iteratie nodig om de afsluitende byte te kopiëren. Een woordlaadinstructie zou vier bytes tegelijk lezen en dit algoritme dat byte voor byte werkt niet implementeren.

UTF-8 kan één teken in meerdere bytes coderen. Deze lus kopieert die bytes ongewijzigd; ze telt geen weergegeven tekens. Arraylussen met pointers kunnen op dezelfde manier met de elementstapgrootte opschuiven zonder telkens opnieuw een geschaalde index te berekenen. Optimaliserende compilers kunnen die omzetting uitvoeren, ook wanneer het C-programma gewone array-indexen gebruikt.

Belangrijkste punten

  • Dereferentie gebruikt het adres dat de pointer bevat.
  • Pointerberekeningen in assembleertaal moeten rekening houden met de elementgrootte.
  • Arrayparameters hebben een afzonderlijke lengte nodig wanneer de procedure een aantal gebruikt.
  • Structuurstapgrootte en veldoffset dienen verschillende delen van een adres.
  • Een lus die een tekenreeks kopieert, moet de afsluitende nulbyte opslaan.
5

Van broncode naar uitvoering

Een programma doorloopt hulpmiddelen die elk andere informatie over de uiteindelijke vorm kennen. Een compiler kiest instructies, een assembler codeert ze, een linker voegt modules samen en een loader bereidt de uitvoering voor. Symbolen en relocaties dragen onvoltooide verwijzingen over tussen de stappen. Door één aanroep van broncode tot het uiteindelijke adres te volgen, worden de rollen van objectbestanden, gedeelde bibliotheken en opstartcode concreet.

5.1 Vertaal broncode naar een relocateerbaar objectbestand

Een bronbestand wordt via meerdere vertaalstappen omgezet in uitvoerbare instructies. Elke stap heeft een andere verantwoordelijkheid en tussenbestanden bewaren informatie die latere stappen nodig hebben.

De compiler kiest instructies

Voor C is het gebruikelijke pad: broncode, assembleertaal, objectbestand, uitvoerbaar bestand en dan een actief proces. Een compiler analyseert broncode, optimaliseert die, wijst registers toe en selecteert instructies voor een doel-ISA. Een compileraanstuurprogramma kan meerdere stappen met één commando uitvoeren.

gcc -S program.c stopt na het produceren van assembleertaal. gcc -c program.c maakt een objectbestand zonder te linken. De doelcompiler bepaalt welke ISA die assembleertaal gebruikt; een RISC-V-programma heeft een gereedschapsketen nodig die zich op RISC-V richt.

Bij -O0 lopen waarden vaak via plaatsen op de stack en volgt de assembleertaal de afzonderlijke broninstructies nauwkeurig. Bij -O2 kunnen registertoewijzing, verwijderd werk en opnieuw gestructureerde lussen die overeenkomst verhullen. Een C-instructie heeft geen enkele vaste vertaling naar assembleertaal.

De assembler codeert en legt vast

De assembler zet instructienamen en registernamen om in bits, werkt pseudo-instructies uit en lost labels op waarvan hij de locatie kan bepalen. Een voorwaartse sprong verwijst naar een label dat later voorkomt. Een assembler kan eerst posities verzamelen en daarna verwijzingen coderen, of plaatsen vastleggen die moeten worden ingevuld zodra het doel bekend is.

Directieven vertellen de assembler hoe hij de uitvoer moet organiseren; de processor voert ze niet uit:

.data
.align 2
count:
.word 5
message:
.string "hi"
.text
.globl main
main:
# instructions follow

.word 5 genereert een gegevenswaarde van 4 bytes. .string "hi" genereert de tekenbytes en een afsluitende nul. Bij de GNU RISC-V-assembler lijnt .align 2 het volgende item uit op een grens van 4 bytes. .globl maakt een symbool zichtbaar voor andere objectbestanden. Labels geven posities een naam; ze reserveren zelf geen bytes.

Wat een objectbestand bewaart

Een relocateerbaar objectbestand bevat doorgaans een header, code in tekstsecties, statische gegevens, een symbolentabel en relocatie-informatie. De symbolentabel onderscheidt namen die hier zijn gedefinieerd van verwijzingen die elders zijn gedefinieerd. Optionele debuginformatie koppelt instructies aan bronregels; -g vraagt die informatie aan.

Veelgebruikte objectformaten zijn ELF, Mach-O en PE/COFF. Het formaat organiseert machinecode en metadata; het staat los van de ISA die de bits van elke instructie definieert.

Voor call printf kan de assembler een auipc/jalr-reeks genereren zonder het uiteindelijke functieadres te kennen. Hij legt een ongedefinieerd symbool en relocatie-items vast zodat de linker de verwijzing kan afmaken. De afstand van een lokale voorwaardelijke sprong kan al bekend zijn wanneer het doel met dezelfde sectie meebeweegt, hoewel latere indelingswijzigingen zoals verkorting een aanpassing kunnen vereisen.

Andere uitvoeringspaden

Een interpreter voert bewerkingen uit via een ander actief programma. Een just-in-timecompiler genereert machinecode tijdens de uitvoering, vaak nadat veelgebruikte code is herkend. Java gebruikt gewoonlijk bytecode gevolgd door interpretatie en JIT-compilatie; Python gebruikt gewoonlijk een interpreter. Een JIT-compiler ruilt compilatiewerk tijdens de uitvoering in voor snellere herhaalde uitvoering.

Een objectbestand is dus meer dan een ruwe instructielijst, maar nog geen volledig actief proces. Niet-opgeloste symbolen en de definitieve indeling vragen nog aandacht van de linker en loader.

Belangrijkste punten

  • De compiler selecteert en optimaliseert instructies voor een doel-ISA.
  • De assembler codeert instructies en werkt pseudo-instructies uit.
  • Directieven ordenen de uitvoer; labels geven posities een naam.
  • Symbolen en relocaties bewaren verwijzingen die latere stappen moeten oplossen.
  • -S stopt bij assembleertaal, terwijl -c stopt bij een objectbestand.
  • Interpreters en JIT-compilers bieden verschillende wegen naar uitvoering.

Een linker bouwt een uitvoerbaar bestand uit objectmodules en bibliotheken. Een loader gebruikt dat bestand om een actief proces te maken. Als je weet welke stap voor een adres verantwoordelijk is, helpt dat zowel relocatie als opstartfouten te verklaren.

Deel modules in en pas verwijzingen aan

De linker plaatst invoersecties in de adresruimte van het uitvoerbare bestand, lost symbooldefinities op en gebruikt relocatie-items om verwijzingen aan te passen. Een naam die door geen enkele gelinkte module is gedefinieerd, veroorzaakt een fout “undefined reference”.

Stel dat tekst begint op 0x00010000 en gegevens op 0x10000000. Object A draagt 0x100 bytes tekst en 0x20 bytes gegevens bij; B volgt direct daarop. De tekst van B begint op 0x00010100 en de gegevens van B op 0x10000020.

Een jal in A op tekstoffset 0x40 heeft adres 0x00010040. Als procB aan het begin van de tekst van B begint, is de offset van de aanroep 0xC0, oftewel 192 bytes, met ra gecodeerd als 0x0C0000EF. Een variabele op offset 0x10 in de gegevens van B komt op 0x10000030. Met gp = 0x10000800 is de offset −2000, wat past in een 12-bitslaadoffset met teken. Dit zijn afzonderlijke berekeningen voor tekst en gegevens.

Kies hoe bibliotheekcode wordt geleverd

Statisch linken kopieert vereiste objectcode van bibliotheken naar het uitvoerbare bestand. Het programma bevat die code, waardoor het bestand groter wordt; om een correctie in een bibliotheek toe te passen moet het opnieuw worden gelinkt. Statische bibliotheken nemen de afhankelijkheid van het programma van de interface van het besturingssysteem niet weg.

Dynamisch linken bewaart bibliotheekcode in gedeelde bestanden, zoals .so, .dll of .dylib. Processen kunnen compatibele codepagina's van een bibliotheek delen. Een compatibele bijgewerkte bibliotheek kan programma's bedienen zonder elk uitvoerbaar bestand opnieuw te linken, maar de vereiste versie moet beschikbaar zijn wanneer die nodig is.

Bij uitgestelde binding in ELF gaat de eerste aanroep via een tussenstuk in een procedurekoppelingstabel (PLT). De dynamische linker lost het doel op en legt het vast in de globale-offsettabel (GOT); latere aanroepen gebruiken dat vastgelegde adres. Andere configuraties lossen symbolen op tijdens het laden.

Positieonafhankelijke code bereikt lokale doelen met relatieve adressering, waaronder auipc-reeksen, en gebruikt indirectie voor externe doelen. De eigen relatieve afstanden van een bibliotheek blijven geldig wanneer die als geheel wordt verplaatst. Voor het binden van externe verwijzingen kan nog relocatiewerk nodig zijn.

De loader maakt de actieve toestand

De loader leest metadata van het uitvoerbare bestand, maakt een adresruimte, koppelt of kopieert code en gegevens, zet argumenten en de initiële stack klaar, initialiseert de vereiste registertoestand en draagt de besturing over aan opstartcode. De opstartcode roept main aan; nadat die terugkeert, vraagt uitvoeringscode om het proces te beëindigen. Moderne systemen kunnen bestandspagina's koppelen en naar behoefte in het geheugen laden.

ecall vraagt een dienst van de omgeving aan. Op RISC-V Linux bevat a7 het nummer van de systeemaanroep en bevatten a0–a5 de argumenten. li a7, 93, li a0, 0, ecall vraagt om afsluiting met status 0; dienst 64 is write. Dienstnummers van simulatoren komen uit de eigen interface van de simulator.

Een bibliotheek die tijdens het linken ontbreekt, veroorzaakt een linkfout. Een vereiste gedeelde bibliotheek die na het linken wordt verwijderd, kan daarentegen het laden verhinderen of een fout veroorzaken bij binding tijdens de uitvoering. Het uitvoerbare bestand kan succesvol zijn gebouwd, ook al kan het nu niet starten.

Belangrijkste punten

  • De linker deelt een uitvoerbaar bestand in en lost symboolverwijzingen op en past ze aan.
  • De loader koppelt dat uitvoerbare bestand aan het geheugen en maakt de initiële procestoestand.
  • Statisch linken kopieert bibliotheekcode; dynamisch linken heeft compatibele gedeelde bibliotheken nodig.
  • Uitgestelde binding legt een opgelost doel vast voor latere aanroepen.
  • Positieonafhankelijkheid behoudt lokale relatieve verwijzingen bij verplaatsing.
  • Dienstnummers voor ecall behoren bij de uitvoeringsomgeving.
6

Afwegingen bij instructiesetontwerp

Instructiesets maken verschillende keuzes over hoeveel werk één instructie uitdrukt. Vergelijk RISC-V en x86 aan de hand van hun operanden, coderingen en adresberekeningen en verbind die keuzes daarna met werkelijke beperkingen. Voor prestaties heb je het aantal uitgevoerde instructies, de CPI en de kloktiming samen nodig; codedichtheid en softwarecompatibiliteit beantwoorden andere vragen. Een bruikbaar oordeel over een ontwerp noemt de werklast en de implementatie die worden vergeleken.

6.1 Vergelijk RISC-V- en x86-instructies

RISC en CISC beschrijven brede benaderingen voor het ontwerpen van instructiesets. Een nuttige vergelijking kijkt naar hoe elk werk voorstelt: operandlocaties, instructielengte, adresberekening en programmaverloop.

Regelmatige bewerkingen of uitgebreidere instructies

RISC-ontwerpen geven de voorkeur aan eenvoudige bewerkingen, veel registers en load-storetoegang. De aanpak ontwikkelde zich vanuit het werk aan de IBM 801 halverwege de jaren 1970 en Berkeley RISC en Stanford MIPS begin jaren 1980. RISC-V, ARM AArch64, MIPS en Power volgen deze brede traditie.

CISC-ontwerpen bieden instructies met variabele lengte, uitgebreidere adressering en bewerkingen die meerdere stappen kunnen combineren. x86 groeide uit de 8086 die in 1978 werd geïntroduceerd; eerdere CISC-families zijn VAX en Motorola 68000.

In de Intel x86-syntaxis leest add eax, [ebx+8] een 32-bitswaarde uit het geheugen en telt die op bij eax. Een equivalente RISC-V-bewerking heeft een laadinstructie gevolgd door een rekenkundige instructie nodig:

lw t0, 8(s1)
add a0, a0, t0

De x86-instructie combineert een geheugentoegang met een optelling. RISC-V geeft ze weer als afzonderlijke instructies. x86 noemt een 32-bitswaarde een dubbelwoord; een woord is daar 16 bits. Een woord in de RV32-voorbeelden is 32 bits.

Codering en adressering

Een x86-instructie is 1 tot 15 bytes lang. RISC-V-basisinstructies nemen 4 bytes in; via C zijn gecomprimeerde vormen van 2 bytes beschikbaar. Regelmatige veldposities vereenvoudigen het decoderen. Variabele lengtes kunnen ruimte besparen door korte coderingen voor veelgebruikte bewerkingen te gebruiken.

RISC-V gebruikt registeroperanden, onmiddellijke operanden, basis-plus-offsetadressen en PC-relatieve adressen. jalr hoort bij adressering op basis van een register, terwijl voorwaardelijke sprongen, jal en auipc de pc gebruiken.

x86 combineert ook een basis, geschaalde index en offset. mov eax, [ebx + esi*4 + 8] leest in één instructie een geïndexeerd element van 4 bytes met een extra offset. RISC-V vormt de geschaalde index en het effectieve adres met afzonderlijke instructies voordat het laadt. In beide processors moet dat werk intern nog steeds worden uitgevoerd.

Registers en vergelijkingen

RISC-V heeft 32 integerregisters, inclusief het vaste nulregister. Conventionele x86-64 heeft 16 algemene registers; 32-bits-x86 heeft er 8. Veel rekenkundige x86-instructies gebruiken een doel dat ook een bron is: add eax, ebx verandert eax. De RISC-V-instructie add met drie operanden kan beide bronwaarden behouden en naar een derde register schrijven.

Rekenkundige x86-instructies en cmp stellen vlaggen in; een latere voorwaardelijke sprong zoals jl test die vlaggen. RISC-V gebruikt blt om rechtstreeks te vergelijken en te springen. ARM AArch64 gebruikt ook vlaggen, dus de aanwezigheid van vlaggen alleen kan een ISA niet als CISC indelen.

Wat verminderd betekent

RISC betekent niet dat een moderne ISA in totaal heel weinig instructies moet hebben. RV32I heeft 40 basisinstructies, maar optionele extensies voegen er veel meer toe. De kern is regelmatige, relatief eenvoudige bewerkingen en coderingen. Gecomprimeerde coderingen laten ook zien dat een vaste instructielengte een ontwerptendens is, geen definitie waaraan elke RISC-instructie moet voldoen.

Belangrijkste punten

  • RISC-V scheidt geheugentoegang van rekenkundige bewerkingen.
  • x86 kan geheugenoperanden en rekenkundige bewerkingen in één instructie combineren.
  • Instructielengte en adresseringswijzen beïnvloeden het decoderen en de codedichtheid.
  • RISC-V vergelijkt binnen een voorwaardelijke sprong; x86 gebruikt doorgaans conditievlaggen.
  • Het aantal instructies alleen bepaalt niet of een ontwerp RISC is.

6.2 Beoordeel snelheid, codeomvang en compatibiliteit

Een instructieset bepaalt mede welk werk een programma uitdrukt, maar de uitvoeringssnelheid hangt ook af van de processor die de set implementeert. Vergelijk gemeten of gemodelleerd programmagedrag, niet het aantal bewerkingen dat één instructie belooft.

Combineer alle drie de prestatiefactoren

CPU-tijd is gelijk aan het aantal uitgevoerde instructies maal het gemiddelde aantal cycli per instructie (CPI) maal de klokcyclustijd. Je kunt ook het aantal uitgevoerde instructies maal de CPI delen door de klokfrequentie. Een kleiner aantal instructies kan teniet worden gedaan door een hogere CPI of een lagere klokfrequentie.

Bij 2 GHz duurt elke cyclus 0,5 ns. Een versie met 1,0 miljard instructies en CPI 2,0 gebruikt 2,0 miljard cycli en duurt 1,0 s. Een andere versie met 1,3 miljard instructies en CPI 1,2 gebruikt 1,56 miljard cycli en duurt 0,78 s. De tweede voert 30% meer instructies uit, maar is ongeveer 1,28 keer zo snel. CPI is een gemiddelde voor dat programma op die implementatie, geen universele eigenschap van een ISA.

Complexe instructies vergen nog steeds intern werk

Veel moderne x86-processors vertalen de meeste instructies naar eenvoudigere interne micro-operaties. Die kunnen vervolgens uitvoeringstechnieken gebruiken die lijken op die in moderne RISC-kernen. De externe ISA blijft van belang voor codering, decodering en compatibiliteit, ook wanneer interne bewerkingen op elkaar lijken.

Een complexe instructie belooft geen vast aantal cycli. Een instructie die een blok kopieert en een eenvoudige kopieerlus kunnen afhankelijk van processor en werklast beter of slechter presteren dan elkaar. De processorversie, gegevensgrootte en het geheugengedrag zijn van belang; een broncode met één instructie kan het resultaat niet bepalen.

Codedichtheid is een afzonderlijke maat

Kortere machinecodereeksen kunnen de opslagbehoefte en belasting van de instructiecache verminderen. Variabele CISC-lengtes helpen vaak bij de dichtheid. De C-extensie van RISC-V biedt 16-bitscoderingen voor veelgebruikte bewerkingen naast 32-bitsinstructies. De specificatie meldt dat die doorgaans voor 50–60% van de instructies worden gebruikt, wat ongeveer 25–30% kleinere code oplevert.

Voor 100 instructies waarvan de helft is gecomprimeerd, gebruiken er 50 elk 2 bytes en 50 elk 4: 300 bytes in plaats van 400. Het aantal uitgevoerde instructies blijft 100. Een omvangvermindering bepaalt op zichzelf geen versnellingsfactor, omdat de uitvoeringstijd van meer afhangt dan de opgehaalde bytes.

Compatibiliteit en hulpmiddelen bepalen mede de keuze

Bestaande x86-binaire bestanden en -bibliotheken maken compatibiliteit zeer waardevol. RISC-V legt basissets vast en voegt extensies toe. RV32I-code kan draaien op een implementatie die RV32I ondersteunt wanneer de ABI en uitvoeringsomgeving overeenkomen; gebruik van M of V voegt die vereisten toe. RV32E is een andere basisset met minder registers en belooft geen RV32I-compatibiliteit.

ARM wordt veel gebruikt in mobiele apparaten en ook in laptops en servers; x86 wordt veel gebruikt in pc's en servers; RISC-V wordt gebruikt in ingebedde systemen, hardwareversnellers, onderzoek en onderwijs. Beschikbaarheid van software, licenties en de kwaliteit van specifieke chips zijn allemaal van belang bij de keuze van een platform.

Handgeschreven assembleertaal kan een kleine kritieke routine helpen of een speciale instructie toegankelijk maken, maar bindt code aan een ISA en vergroot het onderhoudswerk. Optimaliserende compilers kunnen goed registers toewijzen en code herstructureren. Gebruik metingen om te onderbouwen dat je heldere broncode door assembleertaal vervangt.

Belangrijkste punten

  • Vergelijk het aantal uitgevoerde instructies, de CPI en de cyclustijd samen.
  • Micro-operaties veranderen de ISA die zichtbaar is voor software niet.
  • Gecomprimeerde coderingen verminderen het aantal bytes zonder noodzakelijk het aantal uitgevoerde instructies te verminderen.
  • Binaire compatibiliteit vereist ook een passende ABI en omgeving.
  • Meet een specifiek programma en een specifieke implementatie voordat je beweert dat een ISA sneller is.

Instructiesets en assembleertaal: voorbeelden

6 Walkthroughs

1

Volg operanden van een expressie naar het geheugen

15 stappen
Lees een instructie, volg tijdelijke waarden, werk een array bij en bouw een grotere constante op.
  1. 1
    IntroductieVolg telkens één waarde

    Een assembleerinstructie noemt zowel de bewerking als de operanden. Begin met een leesbare registerberekening, haal daarna een waarde uit het geheugen en sla een bijgewerkt resultaat op. Bouw ten slotte een constante op die te groot is voor één onmiddellijke operand. Houd bij elke stap precies bij wat verandert: een doelregister, een groep geheugenbytes of het adres van de volgende instructie.

  2. 2
    UitlegLees de instructieovereenkomst

    De ISA definieert wat add a0, a1, a2 moet doen: de twee bronnen lezen en hun som naar het doel schrijven. De microarchitectuur bepaalt hoe de chip dat verzoek uitvoert. Assembleertaal geeft het verzoek leesbare namen; de assembler produceert de gecodeerde bits. Verschillende chips die dezelfde instructieovereenkomst implementeren moeten hetzelfde zichtbare resultaat geven.

  3. 3
    UitdagingTel instructies na het assembleren

    Een assembler werkt één li uit tot twee machine-instructies, gevolgd door één add. Hoeveel instructiebytes produceren deze twee assembleerregels als er alleen 32-bitscoderingen zijn?

    ---reveal---

    Er zijn drie daadwerkelijke instructies van elk 4 bytes, samen 12 bytes. De tekst telt twee regels, maar de uitwerking bepaalt de omvang van de machinecode. Labels en opmerkingen voegen geen uitgevoerde instructies toe.

  4. 4
    VoorbeeldBewaar de tussensom

    Bereken r = (u + v) - w met u = 18, v = 7, w = 6 in a1, a2, a3:

    add t0, a1, a2
    sub a0, t0, a3
    

    Na de eerste instructie bevat t0 25. Na de tweede bevat a0 19. De drie invoerwaarden blijven beschikbaar omdat geen van beide instructies naar hun registers schrijft. t0 heeft één doel: het eerste resultaat bewaren totdat de aftrekking het leest.

  5. 5
    UitdagingVerander het doel, behoud de bronnen

    Met dezelfde invoerwaarden wordt na de optelling nu sub a3, t0, a3 uitgevoerd. Wat verandert er ten opzichte van het schrijven van het resultaat naar a0?

    ---reveal---

    Het numerieke resultaat is nog steeds 19, maar vervangt de oude waarde 6 in a3. De instructie leest de bronwaarden voordat ze naar het doel schrijft. a0 blijft ongewijzigd, dus een aanroeper die daar het resultaat verwacht, ontvangt dit nieuwe resultaat niet.

  6. 6
    UitlegZet een index om in een adres

    Een array van gehele getallen begint op het adres in s1. Elke RV32 ILP32-int neemt 4 bytes in, dus element 2 begint 8 bytes na de basis en element 5 begint 20 bytes erna. De laadinstructie lw t0, 8(s1) leest de waarde van element 2; ze zet niet het adres zelf in t0. Het basisregister blijft ongewijzigd.

  7. 7
    VoorbeeldLaden, berekenen, opslaan

    Implementeer A[5] = A[2] + r met r = 19 in a0:

    lw t0, 8(s1)
    add t0, t0, a0
    sw t0, 20(s1)
    

    Als A[2] gelijk is aan 11, geeft de laadinstructie 11, geeft de optelling 30 en schrijft de opslaginstructie 30 naar A[5]. Geheugen lezen en schrijven vereist afzonderlijke instructies rond de rekenkundige bewerking. De waarde van A[2] blijft 11.

  8. 8
    UitdagingHerken de verkeerde eenheid in een offset

    Iemand verandert de eerste regel in lw t0, 2(s1) omdat de expressie A[2] noemt. Wat is er misgegaan?

    ---reveal---

    De offset is 2 bytes, geen twee elementen. Element 2 ligt 8 bytes vanaf de basis. Het gewijzigde adres is ook niet natuurlijk uitgelijnd voor een woord als de basis is uitgelijnd, dus de toegang kan mislukken of een speciale behandeling vereisen voordat die überhaupt bruikbare gegevens kan leveren.

  9. 9
    VoorbeeldLees een byte uit een opgeslagen woord

    Sla 0x7193D2A6 op een uitgelijnd adres op een little-endianmachine op. De bytes in oplopende adresvolgorde zijn 0xA6, 0xD2, 0x93, 0x71. Een bytelaadinstructie op offset 1 selecteert 0xD2. lbu levert 210 op, terwijl lb −46 oplevert door bit 7 uit te breiden naar de hoge registerposities. De geheugenbyte is identiek; de laadinstructie bepaalt de interpretatie.

  10. 10
    UitlegBepaal het lage deel met teken

    Bouw nu 0x00003D20 op in een register. De onderste 12 bits zijn 0xD20, die als 12-bits onmiddellijke operand met teken −736 voorstellen. De bovenste stap moet daarom één blok van 4096 bytes hoger beginnen dan de zichtbare 0x3 doet vermoeden. Als je 0x4 kiest, krijg je 0x4000, waarvan je 0x2E0 aftrekt om de gewenste waarde te bereiken.

  11. 11
    VoorbeeldBouw en controleer de constante

    Gebruik:

    lui t1, 0x4
    addi t1, t1, -736
    

    De eerste instructie schrijft 16.384; de tweede trekt 736 af, zodat 15.648, oftewel 0x3D20, overblijft. Het lage deel is een term met een teken, niet alleen cijfers die je op hun plaats plakt. Met li t1, 0x3D20 laat je de assembler de opbouw kiezen, maar de uitwerking neemt nog steeds echte instructies in beslag.

  12. 12
    UitdagingHerstel een opgesplitste constante

    Een opsplitsing gebruikt lui t1, 0x3 en addi t1, t1, -736. Hoe ver ligt het resultaat van 0x3D20 af en welk veld moet worden hersteld?

    ---reveal---

    Het resultaat is 0x2D20, precies 4096 te klein. Verhoog het hoge veld van 0x3 naar 0x4 en behoud de lage term met teken. De term veranderen in positief 0xD20 zou het positieve bereik met teken van addi overschrijden.

  13. 13
    TipVolg een register tot het laatste gebruik

    Schrijf bij het volgen van een reeks een waarde naast het register dat die op dat moment bevat. Streep die pas door wanneer dat register wordt overschreven. Een latere procedureaanroep vormt een risico op overschrijven van tijdelijke registers en argumentregisters, ook als er geen expliciete toewijzing naast de aanroep staat. Sla een waarde die nog nodig is op voordat je die grens overschrijdt.

  14. 14
    UitdagingCombineer de stappen voor adres en waarde

    Met arraybasis s1 is element A[6] gelijk aan 17. t1 bevat de correct opgebouwde constante 0x3D20. Wat verandert lw t0, 24(s1); add t0, t0, t1; sw t0, 4(s1)?

    ---reveal---

    De reeks leest A[6], telt 17 op bij 15.648 en slaat 15.665 op in A[1]. De twee offsets tellen bytes, terwijl de constante een rekenkundige waarde is. Het oorspronkelijke element op index 6 en het basisadres in s1 blijven ongewijzigd.

  15. 15
    SamenvattingVolg de volgende reeks zelf

    Markeer voor een nieuwe assembleerreeks de registers die worden gelezen en beschreven, werk pseudo-instructies uit waarvan de grootte van belang is en vertaal elke geheugenoffset naar bytes. Controleer de uitbreiding van kleine geladen waarden afzonderlijk van de bytevolgorde. Controleer bij het opbouwen van een constante de tussenwaarde voor de hoge bits plus de lage term met teken voordat je het resultaat als gegeven of adres gebruikt.

2

Volg berekeningen naar keuzestructuren en lussen

14 stappen
Isoleer een veld, kies vergelijkingen met teken en volg elk pad door een getelde lus.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieMaak het uitvoeringspad zichtbaar
  2. 2
    VoorbeeldSchuif het veld op zijn plaats
  3. 3
    VoorbeeldBehoud alleen de breedte van het veld
  4. 4
    UitdagingVoorspel een verschuiving met teken
  5. 5
    UitlegVergelijk met de juiste interpretatie
  6. 6
    VoorbeeldOnderscheid een sprong van een aanroep
  7. 7
    UitdagingTest één patroon op twee manieren
  8. 8
    VoorbeeldTeken de twee paden van een if
  9. 9
    UitlegControleer de grens vóór het laden
  10. 10
    VoorbeeldVolg drie uitvoeringen van de lusinhoud
  11. 11
    UitdagingTel het volledige uitgevoerde pad
  12. 12
    VerbandVervang herhaald schalen door een opschuivend adres
  13. 13
    UitdagingBescherm een laadinstructie met kortsluitevaluatie
  14. 14
    SamenvattingControleer waarden en paden samen
3

Codeer een instructie en reconstrueer een voorwaardelijke sprong

12 stappen
Plaats operanden in velden, controleer een gecodeerd woord en bouw een verspreide sprongoffset opnieuw op.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieVertaal in beide richtingen
  2. 2
    UitlegBepaal het formaat en de operanden
  3. 3
    VoorbeeldSchrijf de velden op volledige breedte
  4. 4
    VoorbeeldZet het woord om en decodeer het
  5. 5
    UitdagingBepaal het bronregister van een opslaginstructie
  6. 6
    UitlegMeet de achterwaartse afstand
  7. 7
    VoorbeeldSplits de onmiddellijke operand van de sprong
  8. 8
    VoorbeeldStel de volledige sprong samen
  9. 9
    UitdagingVul de ontbrekende nulbit weer aan
  10. 10
    VerbandBehoud de relatieve afstand als code verschuift
  11. 11
    UitdagingGebruik de opcode om een foutieve decodering af te wijzen
  12. 12
    SamenvattingControleer een machinewoord in omgekeerde richting
4

Behoud waarden tijdens geneste aanroepen

15 stappen
Bouw een correcte wrapper, volg recursieve frames en volg een bytetekenreeks door het geheugen.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieBescherm wat de aanroeper nog nodig heeft
  2. 2
    VoorbeeldSpecificeer het resultaat van de wrapper
  3. 3
    UitlegPas de bewaarafspraken toe
  4. 4
    UitdagingKies de toestand die de aanroep moet overleven
  5. 5
    VoorbeeldReserveer en vul het frame
  6. 6
    VoorbeeldCombineer resultaten voordat je opslagruimte vrijgeeft
  7. 7
    UitlegHerhaal het patroon recursief
  8. 8
    UitdagingDoorloop de opgeslagen argumenten bij terugkeer
  9. 9
    UitlegOnderscheid een adres van het object
  10. 10
    VoorbeeldKies bytebewerkingen voor een tekenreeks
  11. 11
    UitdagingEindig op de juiste adressen
  12. 12
    TipHerstel de opgeslagen waarde, niet het nieuwe resultaat
  13. 13
    VerbandDe levensduur van een object volgt het frame
  14. 14
    UitdagingBehoud een doelpointer tijdens een hulpprocedure
  15. 15
    SamenvattingControleer een volledige aanroep
5

Volg een aanroep van broncode tot opstarten

11 stappen
Volg een symbool door compilatie, assemblage, linken, dynamisch oplossen en het laden van een proces.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieBepaal wat elk hulpmiddel weet
  2. 2
    UitlegCompileer voor een doel-ISA
  3. 3
    VoorbeeldLaat de externe aanroep onafgemaakt
  4. 4
    UitdagingOnderscheid het genereren van gegevens van de uitvoering
  5. 5
    VoorbeeldPlaats de doelmodule
  6. 6
    VoorbeeldPas de relatieve verwijzing aan
  7. 7
    UitlegKies wanneer bibliotheekcode wordt geleverd
  8. 8
    UitdagingBepaal in welke stap de fout optreedt
  9. 9
    VerbandMaak een proces voordat je main aanroept
  10. 10
    UitdagingLeg uit waarom een objectbestand niet het voltooide programma is
  11. 11
    SamenvattingLeid een nieuwe fout terug naar de juiste stap
6

Vergelijk ontwerpen met de juiste maat

10 stappen
Onderscheid zichtbare instructies, gecodeerde bytes en uitvoeringstijd bij het beoordelen van een ISA-keuze.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieHoud drie metingen gescheiden
  2. 2
    VoorbeeldVergelijk de zichtbare geheugenbewerking
  3. 3
    UitlegVerbind codering met de ontwerpkeuze
  4. 4
    UitdagingDeel een kenmerk zorgvuldig in
  5. 5
    VoorbeeldBereken de eerste uitvoeringstijd
  6. 6
    VoorbeeldVergelijk een groter aantal instructies
  7. 7
    UitdagingOnderscheid bytebesparing van versnellingsfactor
  8. 8
    VerbandCompatibiliteit gaat verder dan de ISA
  9. 9
    UitdagingBeoordeel een bewering over optimalisatie in assembleertaal
  10. 10
    SamenvattingNoem het bewijs achter de vergelijking

Instructiesets en assembleertaal: oefenvragen

60 Oefenvragen in 6 sets

Set 1: Registers, geheugen en constanten10 Oefenvragen

Vraag 1 van 10

Twee processors implementeren RV32I met verschillende interne pipelines. Welke gedeelde specificatie bepaalt het resultaat van een RV32I-add-instructie?
  1. De procesindeling van het besturingssysteem
  2. De instructiesetarchitectuur
  3. De registertoewijzing van de compiler
  4. De klokfrequentie van de processor
Toon antwoordVerberg antwoord
Antwoord: B
De ISA definieert het zichtbare effect van add, dus beide implementaties moeten dat resultaat produceren. Hun interne pipelines kunnen verschillen. De klokfrequentie beïnvloedt de tijd, maar verandert niet wat optellen voor een programma betekent.

Vraag 2 van 10

Vóór sub a2, a0, a1 bevat a0 de waarde 28 en a1 de waarde 9. Welke waarde wordt naar a2 geschreven?
  1. 37
  2. −19
  3. 9
  4. 19
Toon antwoordVerberg antwoord
Antwoord: D
De eerste operand is het doel en de aftrekking neemt de tweede operand min de derde. De instructie schrijft dus 28 min 9, oftewel 19, naar a2. Als je de bronnen omwisselt, krijg je −19, maar dat is hier niet hun volgorde.

Vraag 3 van 10

Een RV32 ILP32-array bevat gehele getallen van 4 bytes. Hoeveel bytes na de basis begint element A[7]?
  1. 7 bytes
  2. 11 bytes
  3. 28 bytes
  4. 24 bytes
Toon antwoordVerberg antwoord
Antwoord: C
Elk voorgaand element neemt 4 bytes in, dus index 7 begint 28 bytes vanaf de basis. Als je 7 als byteoffset gebruikt, verwar je een elementindex met een adresoffset. Index nul staat al op de basis en voegt geen extra element aan de telling toe.

Vraag 4 van 10

In little-endiangeheugen slaat sw de waarde 0x86C4217D op op 0x3000. Welke waarde levert lbu t0, 2(s0) op wanneer s0 = 0x3000?
  1. 0x000000C4
  2. 0x00000021
  3. 0xFFFFFFC4
  4. 0x00000086
Toon antwoordVerberg antwoord
Antwoord: A
De opgeslagen bytes zijn 0x7D, 0x21, 0xC4 en 0x86 op oplopende adressen. Offset 2 selecteert 0xC4 en lbu past nuluitbreiding toe tot 0x000000C4. Voor het resultaat met tekenuitbreiding is lb nodig, niet de hier gebruikte bytelaadinstructie zonder teken.

Vraag 5 van 10

Welk hoog veld voor lui en welke onmiddellijke operand met teken voor addi bouwen 0x00002ABC op in RV32?
  1. 0x2 en +2748
  2. 0x2 en −1348
  3. 0x3 en −1348
  4. 0x3 en +2748
Toon antwoordVerberg antwoord
Antwoord: C
Het lage 12-bitspatroon 0xABC stelt −1348 voor. Als je begint bij 0x3000 en 1348 aftrekt, bereik je 0x2ABC, dus het hoge veld is 0x3. Als je 2748 als positieve operand voor addi behandelt, mislukt dat omdat de waarde de positieve grens van een 12-bitsgetal met teken overschrijdt.

Vraag 6 van 10

Een aanroeper bewaart een waarde in t3, roept een hulpprocedure aan die de conventie volgt en heeft daarna de oorspronkelijke waarde nodig. Wat moet de aanroeper regelen?
  1. t3 alleen binnen de hulpprocedure herstellen
  2. t3 behandelen als een door de aangeroepen procedure te bewaren register
  3. t3 opslaan nadat de hulpprocedure terugkeert
  4. De waarde vóór de aanroep beschermen
Toon antwoordVerberg antwoord
Antwoord: D
t3 is een door de aanroeper te bewaren register, dus een hulpprocedure die de conventie volgt mag het overschrijven. De aanroeper moet de nog benodigde waarde opslaan of vóór de aanroep voor een gelijkwaardige bewaarde locatie zorgen. De hulpprocedure hoeft tijdelijke registers niet te herstellen alleen omdat de aanroeper ze nodig heeft.

Vraag 7 van 10

In RV32 wordt auipc t0, 0x2 uitgevoerd op 0x4000, gevolgd door addi t0, t0, -24. Welke uiteindelijke waarde staat in t0?
  1. 0x00005FEC
  2. 0x00005FE8
  3. 0x00006018
  4. 0x00002000
Toon antwoordVerberg antwoord
Antwoord: B
auipc telt 0x2000 op bij het eigen adres, wat 0x6000 oplevert. Na aftrekken van 24 blijft 0x5FE8 over. Als je het adres van de volgende instructie als pc-basis gebruikt, wordt het resultaat ten onrechte 4 bytes hoger.

Vraag 8 van 10

Een byte met 0xB2 wordt eenmaal geladen met lb en eenmaal met lbu. Welke decimale resultaten leveren de twee laadinstructies in die volgorde op?
  1. −78 en 178
  2. 178 en 178
  3. −78 en −78
  4. −50 en 178
Toon antwoordVerberg antwoord
Antwoord: A
Als 8-bitswaarde met teken is 0xB2 gelijk aan 178 min 256, oftewel −78. lb past tekenuitbreiding toe op die waarde, terwijl lbu 178 zonder teken oplevert. Als je beide resultaten positief maakt, negeer je de tekenuitbreiding van de laadinstructie met teken.

Vraag 9 van 10

Een array van structuren gebruikt struct rec { int key; int value; }; onder RV32 ILP32, zonder extra opvulling. De basis is 0x5000. Welk adres wordt gelezen voor A[3].value en welke bytewaarde met teken geeft lb vanaf dat adres terug als het veld 0x000000E9 bevat in little-endiangeheugen?
  1. 0x5018 en −23
  2. 0x501C en 233
  3. 0x5010 en −23
  4. 0x501C en −23
Toon antwoordVerberg antwoord
Antwoord: D
Elke structuur neemt 8 bytes in, dus element 3 begint 24 bytes vanaf de basis; het tweede veld voegt 4 toe, wat 0x501C geeft. De eerste byte daar is 0xE9, waarop lb tekenuitbreiding toepast tot −23. Als je 233 teruggeeft, pas je de uitbreidingsregel zonder teken toe.

Vraag 10 van 10

In RV32 wordt onderstaande reeks uitgevoerd met t1 = 0x7000; het woord op 0x7030 is 6. Welke waarde wordt opgeslagen op 0x7034?

lui t0, 0x4
addi t0, t0, -1984
lw t2, 48(t1)
add t2, t2, t0
sw t2, 52(t1)
  1. 0x00003840
  2. 0x00003846
  3. 0x00004846
  4. 0x00004006
Toon antwoordVerberg antwoord
Antwoord: B
De constante wordt 0x4000 min 0x7C0, oftewel 0x3840. De laadinstructie levert 6, dus de optelling geeft 0x3846, dat de laatste opslaginstructie wegschrijft. 0x3840 is alleen de opgebouwde constante en laat de geladen operand weg.
Set 2: Rekenkundige bewerkingen en paden door een programma10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Welk 32-bits hexadecimaal resultaat produceert RV32I wanneer add 1 optelt bij 0xFFFFFFFF?
  1. 0xFFFFFFFF
  2. 0x80000000
  3. 0x00000000
  4. 0x7FFFFFFF
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Welke echte instructie implementeert bgt a2, a3, Done?
  1. blt a2, a3, Done
  2. blt a3, a2, Done
  3. bge a3, a2, Done
  4. bge a2, a3, Done
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Welke waarde staat na srli t0, a0, 8 en andi t0, t0, 0x1F in t0 als a0 = 0x0000D6B9?
  1. 0x00000016
  2. 0x000000D6
  3. 0x00000019
  4. 0x00000006
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Welke resultaten leveren bij a0 = -11 met teken respectievelijk srai t0, a0, 2 en de gehele deling -11 / 4 in C op?
  1. −3 en −2
  2. −2 en −3
  3. −3 en −3
  4. −2 en −2
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

a0 = 0xFFFFFFFE en a1 = 0x00000003. Welke van blt a0, a1, L en bltu a0, a1, L neemt de sprong?
  1. Alleen bltu
  2. Beide sprongen
  3. Geen van beide
  4. Alleen blt
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een vertaling van if (x != y) r = 8; else r = 3; springt naar Else wanneer de waarden gelijk zijn. Wat komt na de toewijzing van 8 zodat r op het ongelijke pad 8 blijft?
  1. Een voorwaardelijke sprong terug naar de vergelijking
  2. Een sprong naar voorbij het else-deel
  3. Een tweede toewijzing van 3
  4. Een nieuwe lading van x vóór Else
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

De lus while (A[i] == 6) i++; begint met i = 1 en A = {6, 6, 6, 4, 6}. Wat is i wanneer de lus eindigt?
  1. 2
  2. 4
  3. 3
  4. 5
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een bladprocedure voert onderstaande code uit op een array {2, -5, 9} met gehele getallen van 4 bytes, met de basis in a0 en het aantal 3 in a1. Elke weergegeven instructie is één machine-instructie. Welk resultaat en dynamisch aantal uitgevoerde instructies levert dit op?

addi t0, zero, 0
addi t1, zero, 0
L: bge t0, a1, D
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
jal zero, L
D: addi a0, t1, 0
jalr zero, 0(ra)
  1. 6 en 25
  2. 9 en 26
  3. 6 en 24
  4. 6 en 26
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Op 0x8000 wordt jalr ra, 7(t0) uitgevoerd met t0 = 0x903A. Welk doel wordt gebruikt, wat komt in ra en welke instructie kan via dat terugkeeradres terugkeren?
  1. 0x9041; 0x8004; jalr zero, 0(ra)
  2. 0x9040; 0x8004; jalr zero, 0(ra)
  3. 0x9040; 0x8000; jalr zero, 0(ra)
  4. 0x9040; 0x8004; jal zero, 0(ra)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

De C-voorwaarde is p != 0 && *p == 9, met p in a0. Welke volgorde van het programmaverloop behoudt voor het then-pad de voorwaarde en voorkomt dereferentie van een nulpointer?
  1. Wijs nul-p af; laad *p; wijs ongelijkheid met 9 af
  2. Laad *p; wijs nul-p af; vergelijk met 9
  3. Wijs niet-nul-p af; laad *p; wijs ongelijkheid met 9 af
  4. Wijs nul-p af; vergelijk p met 9; laad *p
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 3: Instructievelden en relatieve doelen10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Welk register levert in sw a2, 20(sp) het veld rs1?
  1. a2 (x12)
  2. zero (x0)
  3. ra (x1)
  4. sp (x2)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Wat is het 12-bitspatroon van de onmiddellijke operand voor addi t2, s1, -36?
  1. 1111 1101 1100
  2. 0000 0010 0100
  3. 1111 1101 1011
  4. 1111 1110 1100
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Welke enkele verandering zet een ADD van het R-type om in SUB met dezelfde registers?
  1. Zet opcode op 0100011
  2. Zet funct3 op 100
  3. Zet funct7 op 0100000
  4. Zet rd op de eerste bron
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een voorwaardelijke sprong op 0x308C heeft 0x3068 als doel. Welke byteoffset met teken wordt gecodeerd?
  1. −40
  2. −36
  3. −9
  4. +36
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een jal op 0x1200 heeft offset +84 met teken en doelregister ra. Welk sprongdoel en terugkeeradres ontstaan?
  1. 0x1258 en 0x1204
  2. 0x1254 en 0x1204
  3. 0x1254 en 0x1258
  4. 0x1350 en 0x1204
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Welk woord codeert addi t2, s1, -36 met velden van het I-type? Hierbij zijn t2 = x7, s1 = x9, de ADDI-opcode is 0x13 en funct3 = 000.
  1. 0xFDC48393
  2. 0x02448393
  3. 0xFDC38493
  4. 0xFDC483B3
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Decodeer 0xFEC12623. De opcode 0x23 selecteert een opslaginstructie en funct3 = 010 selecteert een woord. Welke assembleerinstructie stelt dit voor?
  1. sw a2, -12(sp)
  2. lw a2, -20(sp)
  3. sw sp, -20(a2)
  4. sw a2, -20(sp)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een bne a2, zero, Loop van het B-type staat op 0x4018, met Loop op 0x400C. Welk gecodeerd woord bereikt Loop met opcode 0x63 en funct3 = 001?
  1. 0xFE0618E3
  2. 0x00061663
  3. 0xFE061AE3
  4. 0xFE061CE3
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Op adres 0x6000 wordt het woord 0x000022B7 gevolgd door 0xFE028293 op 0x6004. Welke waarde blijft in t0 achter? Deze woorden decoderen respectievelijk als LUI van het U-type en ADDI van het I-type.
  1. 0x00002020
  2. 0x00001FDC
  3. 0x00007FE0
  4. 0x00001FE0
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een voorwaardelijke sprong en het doel ervan beginnen in dezelfde sectie op offsets 0x30 en 0x54. De sectie verhuist van 0x9000 naar 0xB000 zonder de interne indeling te veranderen. Welke sprongoffset en welk doeladres ontstaan?
  1. +0x24 en 0x9054
  2. +0x2024 en 0xB054
  3. +0x24 en 0xB054
  4. +0x20 en 0xB054
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 4: Aanroepen, recursie en toegang via pointers10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een RISC-V-procedure die de conventie volgt verandert s4. Welke waarde moet s4 bevatten wanneer de procedure terugkeert?
  1. De binnengekomen s4-waarde van de aanroeper
  2. Het eerste resultaat van de procedure
  3. Het adres na de aanroep
  4. Het huidige adres van de stacktop
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Welke instructie laat a0 in RV32 ILP32 naar het volgende element wijzen wanneer het een int * bevat?
  1. addi a0, a0, 1
  2. addi a0, a0, 2
  3. addi a0, a0, 8
  4. addi a0, a0, 4
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een procedure begint met sp = 0x8000, reserveert 32 bytes en voert daarna sw ra, 28(sp) uit. Op welk adres begint het opgeslagen woord?
  1. 0x7FE0
  2. 0x801C
  3. 0x7FFC
  4. 0x7FF0
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een bladprocedure ontvangt een argument in a0, berekent een resultaat in s1 en herstelt vóór terugkeer de binnengekomen waarde van s1. Wat moet ze vóór dat herstel doen om het berekende gehele getal terug te geven?
  1. De a0 van de aanroeper bij elke uitgang opslaan
  2. De berekende waarde in s1 naar a0 verplaatsen
  3. s1 herstellen en daarna s1 naar a0 kopiëren
  4. Terugkeren met de berekende waarde in s1
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een lus voor tekenreeksen slaat telkens één byte op, verhoogt beide pointers en springt terug als die byte niet nul is. Hoeveel iteraties kopiëren “logic” inclusief de afsluitende C-nulbyte?
  1. 5
  2. 6
  3. 7
  4. 10
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een procedure die andere procedures aanroept en de standaard-ABI volgt, hoeft alleen ra en s2 op te slaan, elk één woord. Wat is de kleinste positieve framegrootte die de vereiste stackuitlijning behoudt?
  1. 4 bytes
  2. 8 bytes
  3. 16 bytes
  4. 12 bytes
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een functie ontvangt n in a0 en moet helper(n) + n teruggeven. helper volgt de ABI en mag alle door de aanroeper te bewaren registers overschrijven. Ook de binnengekomen ra van de functie moet behouden blijven. Welk plan werkt met een frame van 16 bytes?
  1. Sla ra en n op; roep aan; laad n opnieuw in t0; tel op; herstel ra en sp
  2. Sla alleen ra op; kopieer n naar t0; roep aan; tel t0 op; herstel ra en sp
  3. Sla alleen n op; roep aan; laad n opnieuw; tel op; herstel sp; keer terug via ra
  4. Sla ra en n op; roep aan; herstel n in a0; tel a0 bij zichzelf op; herstel ra
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een recursieve functie reserveert bij elke binnenkomst een frame van 16 bytes, slaat n en ra op, geeft 1 terug voor n < 2 en geeft anders n * f(n - 1) terug. Welk resultaat en welke laagste waarde van sp ontstaan met beginwaarden n = 4 en sp = 0x9000?
  1. 24 en 0x8FD0
  2. 6 en 0x8FC0
  3. 24 en 0x8FB0
  4. 24 en 0x8FC0
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Onder RV32 ILP32 bevat het geheugen op 0xA000 de pointerwaarde 0xB000. Op 0xB000 begint een array van gehele getallen van 4 bytes, waarbij element 4 gelijk is aan 27. Welke reeks laat met a0 = 0xA000 dat element achter in a0?
  1. lw a0, 16(a0); lw a0, 0(a0)
  2. lw t0, 0(a0); lw a0, 16(t0)
  3. addi t0, a0, 16; lw a0, 0(t0)
  4. lw t0, 0(a0); lw a0, 4(t0)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een procedure reserveert 32 bytes, slaat ra op bij 28(sp) en s0 bij 24(sp) en roept daarna een hulpprocedure aan. Bij het verlaten telt ze eerst 16 op bij sp, laadt ze vervolgens vanaf 24(sp) en 28(sp), telt ze nog eens 16 op en keert ze terug. Waarom kan dit mislukken, ook al eindigt sp met de oorspronkelijke waarde?
  1. De laadinstructies gebruiken adressen die 16 bytes te hoog zijn
  2. De hulpprocedure moet elk tijdelijk register bewaren
  3. Een frame kan geen ongebruikte bytes bevatten
  4. De uiteindelijke stackpointer is niet uitgelijnd
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 5: Bouw, link en start een programma10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Welk bestand produceert gcc -c module.c zonder te linken?
  1. Een uitvoerbaar procesbeeld
  2. Een assembleerbronbestand
  3. Een relocateerbaar objectbestand
  4. Een symboolcache voor gedeelde bibliotheken
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Welk onderdeel koppelt uitvoerbare code en gegevens aan een nieuw proces en bereidt de initiële stack voor?
  1. De compiler
  2. De loader
  3. De assembler
  4. De statische linker
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een objectbestand definieert main maar roept mix aan, dat in een tweede objectbestand is gedefinieerd. Wat stelt de linker in staat die aanroep aan de definitie te koppelen?
  1. De debugregeltabel en posities in het bronbestand
  2. De instructieopcodes en een gedeelde registerconventie
  3. De initiële stackindeling en het startpunt van het uitvoerbare bestand
  4. De symboolinformatie en relocatieregistraties voor de aanroep
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Er wordt verwezen naar een lokaal label voordat het later in dezelfde assembleersectie wordt gedefinieerd. Hoe kan een assembler die voorwaardelijke sprong afmaken?
  1. De verwijzing vastleggen en de positie later oplossen
  2. De loader het bronlabel laten analyseren
  3. De labelnaam als onmiddellijk geheel getal behandelen
  4. De sprong met een blijvende offset nul genereren
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een programma is statisch gelinkt met de implementatie van filter uit een bibliotheek. Daarna wordt het bibliotheekarchief gecorrigeerd, maar het uitvoerbare bestand blijft ongewijzigd. Welke code bevat het uitvoerbare bestand voor filter?
  1. De oorspronkelijke code die tijdens het linken is gekopieerd
  2. De gecorrigeerde archiefcode bij de volgende aanroep
  3. Een opnieuw geïnterpreteerde versie van de broncode
  4. Een PLT-tussenstuk dat gedeelde vervangende code laadt
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een RISC-V-programma voor Linux zet 93 in a7, 7 in a0 en voert daarna ecall uit. Welke dienst wordt aangevraagd?
  1. 7 bytes naar een standaardstroom schrijven
  2. 93 aan de huidige aanroeper teruggeven
  3. Het proces beëindigen met status 7
  4. Een stackframe van 7 bytes reserveren
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een linker plaatst tekst op 0x20000 en gegevens op 0x30000000. Module P draagt 0x180 bytes tekst en 0x30 bytes gegevens bij; Q volgt direct. Een aanroep in P staat op tekstoffset 0x70; het doel in Q staat op tekstoffset 0x20 binnen Q. De variabele van Q staat op gegevensoffset 0x14 binnen Q. Wat zijn de byteoffset van de aanroep en het adres van de variabele?
  1. +0x1A0 en 0x30000044
  2. +0x130 en 0x30000194
  3. +0x12C en 0x30000044
  4. +0x130 en 0x30000044
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een RV32I-codeoverzicht op 0x1000 bevat de volgende uitwerkingen: li t0, 5000 gebruikt twee instructies, mv a0, t0 één en call Helper een niet-verkort paar van twee instructies. Helper keert normaal terug. Op welk adres wordt de uitvoering na de aanroep hervat en hoeveel codebytes gaan aan die hervatting vooraf?
  1. 0x100C en 12 bytes
  2. 0x1014 en 20 bytes
  3. 0x1010 en 16 bytes
  4. 0x1018 en 24 bytes
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een bibliotheekfunctie begint op offset 0x2C0 in een gedeelde bibliotheek. Een lokale aanroeper staat op offset 0x90; een functiepointer buiten de bibliotheek is opgeslagen in een GOT-item. De basis van de bibliotheek verandert van 0x40000000 naar 0x50000000. Welke beschrijving behandelt beide verwijzingen correct?
  1. Tel 0x10000000 op bij de lokale offset en behoud elk GOT-item
  2. Behoud het lokale doel op 0x400002C0; relocateer alleen de aanroepinstructie
  3. Trek 0x10000000 af van de lokale afstand en maak de hele GOT leeg
  4. Behoud de lokale afstand +0x230; bind het externe GOT-doel waar nodig
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een uitvoerbaar bestand is succesvol gelinkt tegen een gedeelde bibliotheek. Later ontbreekt die bibliotheek en mislukt het laden van het uitvoerbare bestand vóór main. Wat verklaart de fout en wat zou anders zijn voor code uit die bibliotheek die statisch is opgenomen?
  1. Laden vereist het gedeelde bestand; opgenomen code staat al in het uitvoerbare bestand
  2. Linken wordt bij het starten herhaald; opgenomen code vereist nog het oorspronkelijke archief
  3. Het invoerobject ontbreekt; opgenomen code vereist nog elk invoerobject
  4. Het bestand heeft geen machinecode; opgenomen code omzeilt de procesloader
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 6: Beoordeel ontwerpen en analyseer assembleertaal10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Welk paar RISC-V-instructies voert de optelling met een geheugenwaarde uit die x86 add eax, [ebx+12] uitdrukt, waarbij eax overeenkomt met a0 en ebx met s1?
  1. addi a0, a0, 12; add a0, a0, s1
  2. lw a0, 12(s1); add a0, a0, a0
  3. sw a0, 12(s1); add a0, a0, s1
  4. lw t0, 12(s1); add a0, a0, t0
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een processor heeft een load-store-ISA en conditievlaggen. Wat kun je concluderen over de bewering dat vlaggen de processor CISC maken?
  1. Vlaggen impliceren rekenkunde met twee operanden, dus de bewering klopt.
  2. Vlaggen komen voor in RISC-ontwerpen, dus de bewering klopt niet.
  3. Een load-store-ISA kan geen vlaggen gebruiken, dus de beschrijving is tegenstrijdig.
  4. Vlaggen impliceren gecombineerde geheugenberekeningen, dus de bewering klopt.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Versie P voert 0,8 miljard instructies uit bij CPI 1,5 en 2 GHz. Versie Q voert 1,0 miljard instructies uit bij CPI 1,0 en 2 GHz. Welke uitvoeringstijden ontstaan voor P en Q?
  1. 0,60 s en 0,50 s
  2. 0,40 s en 0,50 s
  3. 0,60 s en 1,00 s
  4. 1,20 s en 1,00 s
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een programma heeft 120 machine-instructies. Als er 72 een 16-bitscodering gebruiken en de rest een 32-bitscodering, hoeveel codebytes neemt het programma dan in?
  1. 240 bytes
  2. 384 bytes
  3. 336 bytes
  4. 408 bytes
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Ontwerp P voert 1,2 miljard instructies uit bij CPI 1,1 en 2,2 GHz. Ontwerp Q voert 0,9 miljard instructies uit bij CPI 1,8 en 2,7 GHz. Een voorgestelde optimalisatie vermindert het aantal instructies van Q met 10%, maar verhoogt de CPI met 20%, terwijl de frequentie gelijk blijft. Hoe verhoudt het geoptimaliseerde Q zich tot P?
  1. Q duurt 0,648 s en is langzamer dan P
  2. Q duurt 0,54 s en is sneller dan P
  3. Q duurt 0,60 s en is even snel als P
  4. Q duurt 0,72 s en is langzamer dan P
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een routine van 200 instructies gebruikt oorspronkelijk alleen 32-bitscoderingen. Nadat 110 coderingen 16 bits worden, blijven de instructiereeks en de gemeten uitvoeringstijd onveranderd. Wat zijn de nieuwe omvang en de gerechtvaardigde conclusie over de prestaties?
  1. 580 bytes; 27,5% snellere uitvoering
  2. 620 bytes; minder bytes bij onveranderde gemeten snelheid
  3. 400 bytes; half zoveel uitgevoerde instructies
  4. 580 bytes; minder bytes bij onveranderde gemeten snelheid
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een binair bestand gebruikt RV32I plus mul, verwacht de ILP32-aanroepconventie en roept RISC-V Linux-systeemaanroepen aan. Een bord vermeldt RV32I, gebruikt ILP32 en draait een simulator met een eigen diensteninterface. Aan welke aanvullende vereisten moet worden voldaan voordat dat binaire bestand volgens de bedoeling kan draaien?
  1. Ondersteuning voor mul en de huidige diensteninterface van de simulator
  2. Ondersteuning voor mul en de verwachte diensteninterface van Linux
  3. Alleen RV32I en de verwachte diensteninterface van Linux
  4. Ondersteuning voor mul en alleen de passende ILP32-conventie
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een RV32 ILP32-array bevat struct sample { int id; int reading; }; zonder extra opvulling. Een pointer naar element 2 staat in a0. De meetwaarde van het volgende element is 31. Welke adresberekening en laadinstructie halen die meetwaarde op zonder a0 te veranderen?
  1. addi t0, a0, 4; lw t1, 4(t0)
  2. addi t0, a0, 8; lw t1, 0(t0)
  3. addi t0, a0, 8; lw t1, 4(t0)
  4. addi t0, a0, 12; lw t1, 4(t0)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Deze bladprocedure moet de som van n gehele getallen teruggeven, waarbij n niet-negatief is. Ze wordt aangeroepen met n = 0 en een adres dat niet mag worden gelezen. Welke minimale aanpassing van het programmaverloop voorkomt de ongeldige toegang en behoudt het gedrag bij een positief aantal?

addi t1, zero, 0
L: lw t0, 0(a0)
add t1, t1, t0
addi a0, a0, 4
addi a1, a1, -1
bne a1, zero, L
D: addi a0, t1, 0
ret
  1. Gebruik een bytelaadinstructie zonder teken in de lus
  2. Voeg beq a1, zero, D in vóór L
  3. Verander de laatste bne in een blt met teken
  4. Initialiseer t1 met het eerste array-element
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een handgeschreven routine vermindert het aantal uitgevoerde instructies van 50 miljoen naar 40 miljoen op dezelfde 2 GHz-processor. De gemiddelde CPI stijgt van 1,2 naar 1,6. Wat gebeurt er met de tijd en wat laat dat zien over de bewering over het aantal instructies?
  1. Die daalt van 30 ms naar 24 ms; de vermindering van het aantal bepaalt de tijd
  2. Die stijgt van 60 ms naar 64 ms; de CPI doet de vermindering teniet
  3. Die stijgt van 30 ms naar 32 ms; de CPI doet de vermindering teniet
  4. Die stijgt van 24 ms naar 32 ms; de CPI doet de vermindering teniet
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Kom je niet uit je eigen opgave? Huiswerkhulp

Kernbegrippen

82 Kernbegrippen, elk met een definitie

A

Aanroepconventie
Een aanroepconventie is de afspraak die locaties voor argumenten en resultaten en het bewaren van registers tijdens procedureaanroepen specificeert.
RISC-V-procedures voor gehele getallen geven hun eerste argumenten door in a0 tot en met a7.
Adres laden
Adres laden is een assemblerbewerking die het adres van een label als registerwaarde vormt zonder het daar opgeslagen object te lezen.
Na la t0, Item is een afzonderlijke laadinstructie nodig om de waarde op Item te lezen.
Adressering met geschaalde index
Adressering met geschaalde index combineert een basis met een index die met een stapgrootte is vermenigvuldigd en telt daar vaak een offset bij op om een geheugenadres te vormen.
Een x86-geheugenoperand kan in één instructie de basis plus vier keer een index plus 8 uitdrukken.
Afsluitende nulbyte
Een afsluitende nulbyte is de nulbyte die het einde van een C-bytetekenreeks markeert en opslagruimte inneemt na de laatste byte die niet nul is.
Een lus die een tekenreeks kopieert slaat de afsluitende byte op, zodat ook het doel een herkenbaar einde heeft.
Assembleertaal
Assembleertaal is leesbare, ISA-specifieke tekst die instructies en operanden benoemt voordat een assembler ze als bits codeert.
De tekst add a0, a1, a2 benoemt een bewerking met een gedefinieerde RISC-V-machinecodecodering.
Assembler
Een assembler zet assembleertekst om naar gecodeerde instructies en gegevens en legt daarbij symbolen en verwijzingen vast om later op te lossen.
Een assembler werkt call uit en kan relocatie-items achterlaten voor een functie die in een andere module is gedefinieerd.
Assemblerdirectief
Een assemblerdirectief stuurt het genereren van gegevens, sectiekeuze, uitlijning of symboolmetadata zonder door de processor te worden uitgevoerd.
Het directief .word 5 genereert een waarde van 4 bytes, terwijl .text selecteert waar de daaropvolgende code thuishoort.

B

Basis-plus-offsetadressering
Basis-plus-offsetadressering vormt een effectief geheugenadres door een constante offset met teken op te tellen bij de waarde in een basisregister.
In lw t0, 20(s1) begint het geladen woord 20 bytes voorbij het adres dat in s1 is opgeslagen.
Basisblok
Een basisblok is een rechtlijnige instructiereeks die bij normaal programmaverloop aan het begin wordt binnengegaan en alleen aan het einde wordt verlaten.
Een label dat het doel van een voorwaardelijke sprong is, begint een nieuw blok dat een compiler afzonderlijk kan analyseren.
Binaire applicatie-interface
Een binaire applicatie-interface is de softwareafspraak over aanroepconventies, gegevensindeling en andere regels voor samenwerking tussen binaire onderdelen.
Overeenkomst in ISA kan een verschil van inzicht over welk register de terugkeerwaarde van een functie bevat niet herstellen.
Binaire compatibiliteit
Binaire compatibiliteit maakt het mogelijk bestaande software in machinecode zonder hercompilatie uit te voeren als aan de vereisten voor ISA, ABI en omgeving is voldaan.
Het gebruik van een optionele extensie voegt een hardwarevereiste toe naast ondersteuning voor de basis-ISA.
Bitmasker
Een bitmasker is een patroon dat met een bitsgewijze bewerking wordt gebruikt om bepaalde posities in een ander patroon te selecteren, op nul te zetten, aan te zetten of om te keren.
EN met 0xFF behoudt de onderste byte en zet alle hogere registerposities op nul.
Bladprocedure
Een bladprocedure is een procedure die geen andere procedures aanroept, waardoor ze haar binnengekomen terugkeeradres onaangeroerd kan laten.
Een bladprocedure die alleen argumentregisters en tijdelijke registers gebruikt, kan mogelijk terugkeren zonder een stackframe te reserveren.
Byteadressering
Byteadressering is een geheugenorganisatie waarbij elk adres één byte aanduidt, zodat grotere waarden opeenvolgende adressen beslaan.
Twee aangrenzende 32-bitsarray-elementen beginnen op adressen die 4 verschillen, niet 1.

C

CISC
CISC is een aanpak voor instructiesetontwerp die uitgebreidere bewerkingen en adresseringsvormen ondersteunt, doorgaans met coderingen van variabele lengte.
Een x86-instructie kan het lezen van een geheugenoperand combineren met het optellen ervan bij een register.
Codedichtheid
Codedichtheid beschrijft hoe compact machine-instructies een programma voorstellen en wordt gewoonlijk vergeleken via het aantal bytes dat nodig is voor de code.
Gecomprimeerde instructies kunnen het aantal codebytes verminderen terwijl het aantal uitgevoerde bewerkingen gelijk blijft.
Compiler
Een compiler vertaalt een programma naar een andere representatie en selecteert en optimaliseert gewoonlijk machine-instructies voor een doel-ISA.
Een compiler voor RISC-V kan herhaalde indexberekeningen vervangen door een verhoging van een pointer.
Conditievlaggen
Conditievlaggen zijn toestandsbits die eigenschappen van een resultaat of vergelijking vastleggen die latere instructies kunnen testen.
x86 kan registers vergelijken en daarna op basis van vlaggen springen, terwijl RISC-V-sprongen rechtstreeks vergelijken.
Cycli per instructie
Cycli per instructie is het gemiddelde aantal klokcycli per uitgevoerde instructie voor een bepaald programma en een bepaalde implementatie.
Een programma met meer instructies kan eerder klaar zijn als de CPI voldoende lager is.

D

Dereferentie
Een dereferentie benadert het object dat door een pointerwaarde wordt aangeduid en wordt geïmplementeerd met een passende laad- of opslaginstructie.
lw t0, 0(a0) leest een geheel getal van 4 bytes via het adres dat op dat moment in a0 staat.
Disassemblage
Disassemblage is het terug omzetten van gecodeerde instructiebits naar instructienamen en operanden met de decodeerregels van de ISA.
Een disassembler bepaalt de opcode voordat hij andere bits als registers of delen van onmiddellijke operanden behandelt.
Door de aangeroepen procedure te bewaren register
Een door de aangeroepen procedure te bewaren register moet na een aanroep zijn binnengekomen waarde behouden, waardoor een procedure die het verandert de waarde moet herstellen.
Een functie mag s1 intern gebruiken als ze vóór terugkeer de oorspronkelijke s1 van de aanroeper herstelt.
Door de aanroeper te bewaren register
Een door de aanroeper te bewaren register mag door een aangeroepen procedure worden overschreven, dus de aanroeper moet elke huidige waarde beschermen die daarna nodig is.
Een nog benodigde waarde in t2 moet worden opgeslagen vóór een hulpprocedure die dat register mag gebruiken.
Doorgaan naar de volgende instructie
Doorgaan naar de volgende instructie is het voortzetten van de uitvoering in de volgende instructie of het volgende codeblok omdat geen overdracht van de besturing dat overslaat.
Een vertaling van if-else heeft na het then-deel een sprong nodig om te voorkomen dat de uitvoering doorloopt naar het else-deel.
Dynamisch aantal uitgevoerde instructies
Het dynamische aantal uitgevoerde instructies is het aantal instructies dat daadwerkelijk wordt uitgevoerd op het pad van een programma, inclusief herhaalde lusdoorgangen.
De laatste mislukte test van een lus telt mee in het aantal uitgevoerde instructies, ook al draait de lusinhoud niet opnieuw.
Dynamisch linken
Dynamisch linken verbindt een programma tijdens het laden of de uitvoering met afzonderlijke gedeelde bibliotheekcode en vereist dat compatibele bibliotheken beschikbaar zijn.
Een ontbrekende gedeelde bibliotheek kan verhinderen dat een eerder gelinkt uitvoerbaar bestand start.

E

Elementstapgrootte
Een elementstapgrootte is de afstand in bytes tussen overeenkomstige posities in opeenvolgende elementen van een array of herhaalde indeling.
Een array van structuren met twee ints heeft een stapgrootte van 8 bytes, terwijl het tweede veld offset 4 heeft.
Epiloog
Een epiloog is de afsluitende reeks die opgeslagen toestand herstelt, het huidige stackframe vrijgeeft en de besturing teruggeeft aan de aanroeper.
Door registers te herstellen voordat sp wordt verhoogd, blijven hun offsets relatief aan het gereserveerde frame.

F

Functieveld
Een functieveld verfijnt een opcode om een specifieke bewerking te selecteren, zoals optellen of aftrekken binnen registerberekeningen.
Dezelfde registers en opcode kunnen ADD of SUB voorstellen, afhankelijk van de funct7-bits.

G

Gecomprimeerde instructie
Een gecomprimeerde instructie is een kortere codering van een bewerking, zoals een 16-bitsvorm uit RISC-V C die naast 32-bitsinstructies wordt gebruikt.
Als de helft van 100 instructies 2 bytes gebruikt, is de totale codeomvang 300 bytes in plaats van 400.

I

Impliciete bit
Een impliciete bit is een waarde die uit een coderingsregel wordt gereconstrueerd zonder zelf een opgeslagen bitpositie in te nemen.
De laagste bit van een sprongoffset van het B-type is impliciet nul omdat de afstand in bytes even is.
Indirecte sprong
Een indirecte sprong haalt het doel uit een berekening op basis van een register, zodat een opgeslagen adres de volgende instructie kan kiezen.
jalr kan een aanroep via een functiepointer, een terugkeer of een keuze via een sprongtabel implementeren.
Instellen bij kleiner dan
Instellen bij kleiner dan is een vergelijkingsbewerking die 1 schrijft wanneer de eerste bron kleiner is dan de tweede en anders 0 schrijft.
slt en sltu kunnen verschillen wanneer de hoogste bit van een bron de bron verschillende waarden met en zonder teken geeft.
Instructieformaat
Een instructieformaat is de indeling van velden binnen een gecodeerde instructie, die bitposities toewijst aan operanden en besturing.
Het S-type heeft een opgesplitste onmiddellijke operand waar het R-type een functieveld en doelregister zou hebben.
Instructiesetarchitectuur
Een instructiesetarchitectuur is de overeenkomst die de instructies, registers en het machinegedrag definieert die zichtbaar zijn voor software.
Twee processors kunnen dezelfde ISA implementeren met verschillende interne schakelingen en uitvoeringssnelheden.
Integerregister
Een integerregister is een kleine opslagplaats in de processor die een bitpatroon bevat voor bewerkingen met gehele getallen of adresberekeningen.
In RV32I bevat elk van de 32 integerregisters 32 bits, of het nu voor een waarde of een adres wordt gebruikt.

J

Just-in-timecompilatie
Just-in-timecompilatie genereert machinecode tijdens de uitvoering en richt de compilatie-inspanning vaak op code die herhaaldelijk wordt uitgevoerd.
Een virtuele machine kan bytecode eerst interpreteren en daarna een vaak uitgevoerde methode compileren.

K

Kortsluitevaluatie
Kortsluitevaluatie stopt met het testen van een samengestelde voorwaarde zodra de totale waarheidswaarde al vaststaat.
Een onware eerste operand van && voorkomt in C dat de tweede operand wordt geëvalueerd.

L

Levensduur van een object
De levensduur van een object is de periode waarin de opslag ervan een geldig object voorstelt dat het programma mag benaderen.
Een pointer naar een lokaal object teruggeven houdt dat object niet in leven nadat de functie terugkeert.
Linker
Een linker combineert objectmodules en bibliotheken, deelt de inhoud van een uitvoerbaar bestand in, lost symbolen op en past adresafhankelijke verwijzingen aan.
Een fout voor een ongedefinieerde verwijzing betekent dat de definitieve link een vereiste symbooldefinitie niet kon vinden.
Little-endianbytevolgorde
De little-endianbytevolgorde slaat de minst significante byte van een waarde met meerdere bytes op het laagste geheugenadres op.
Een woordopslag van 0x12345678 plaatst 0x78 in de eerste byte en 0x12 in de vierde.
Load-storearchitectuur
Een load-storearchitectuur gebruikt specifieke instructies voor geheugenoverdracht, terwijl rekenkundige en logische bewerkingen op registerwaarden en onmiddellijke operanden werken.
Om een array-element op te tellen laadt RISC-V het in een register voordat een rekenkundige instructie wordt uitgevoerd.
Loader
Een loader bereidt een uitvoerbaar bestand voor als actief proces door de inhoud aan het geheugen te koppelen en de initiële geheugen- en registertoestand in te stellen.
Het opstarten begint pas nadat de code, gegevens en initiële stack van het programma zijn voorbereid.
Logische verschuiving naar rechts
Een logische verschuiving naar rechts verplaatst bits naar lagere posities en vult vrijgekomen hoge posities met nullen, wat schalen zonder teken ondersteunt.
Een logische verschuiving naar rechts van een negatief patroon met teken kan een groot positief resultaat opleveren.
Lusgrens
Een lusgrens beperkt de iteraties die door de vergelijking van een lus worden toegestaan en bepaalt welke indexen of adressen de lusinhoud kan benaderen.
Een test die de lus verlaat wanneer de index de lengte bereikt, houdt een array-index onder die lengte.

M

Micro-operatie
Een micro-operatie is een interne bewerking waarin een processor een ISA-instructie kan vertalen voor uitvoering door de processorkern.
Eén extern zichtbare x86-instructie kan meerdere eenvoudigere interne micro-operaties vereisen.
Microarchitectuur
Een microarchitectuur is het interne ontwerp van een processor voor de implementatie van de bewerkingen en het gedrag die de instructieset vereist.
Door de uitvoeringshardware te veranderen kan een chip sneller worden terwijl de bestaande machinecode-interface behouden blijft.

N

Natuurlijke uitlijning
Natuurlijke uitlijning plaatst een geheugentoegang op een adres dat deelbaar is door de toegangsgrootte, zoals een veelvoud van 4 voor een woord.
Een compiler kan opvulling invoegen zodat een half woord of woord op een passend uitgelijnd adres begint.
Nibble
Een nibble is een groep van 4 bits die overeenkomt met één hexadecimaal cijfer en het omzetten van binaire instructiewoorden naar hexadecimaal vergemakkelijkt.
Een volledige 32-bitsinstructie wordt verdeeld in acht nibbles nadat alle velden zijn samengevoegd.
Nulregister
Het nulregister is het RISC-V-register x0, dat bij lezen altijd nul geeft en elke waarde weggooit die een instructie ernaartoe schrijft.
Een sprong kan zijn terugkeeradres naar zero schrijven als het programma geen terugkeeradres hoeft op te slaan.
Nuluitbreiding
Nuluitbreiding maakt een bitpatroon breder door nieuwe hoge posities met nullen te vullen, waarbij de waarde geïnterpreteerd zonder teken behouden blijft.
Het laden van byte 0xF0 met lbu produceert de positieve waarde 240 in een 32-bitsregister.

O

Onmiddellijke operand
Een onmiddellijke operand is een constante die door bits binnen een instructie wordt voorgesteld en beschikbaar is zonder die eerst uit het geheugen op te halen.
De laatste operand in addi t0, t0, 1 is als onmiddellijke operand in de instructie gecodeerd.
Onmiddellijke operand voor de hoge bits
Een onmiddellijke operand voor de hoge bits levert een 20-bitsveld dat lui boven 12 lage nullen plaatst bij het opbouwen van een RV32-registerwaarde.
Een daaropvolgende addi kan een laag deel met teken leveren om een volledige 32-bitsconstante te vormen.
Opcode
Een opcode is een instructieveld dat een bewerkingsgroep aanduidt en helpt bepalen welke indeling wordt gebruikt om de andere bits te decoderen.
De onderste 7 bits onderscheiden een RV32I-woordopslag van een rekenkundige registerinstructie.
Overloop
Overloop treedt op wanneer een rekenkundig resultaat buiten het weer te geven bereik voor de gekozen breedte en interpretatie met of zonder teken valt.
Optellen in RV32 behoudt de onderste resultaatbits, ook wanneer de som met teken niet in 32 bits past.

P

PC-relatief adres
Een PC-relatief adres wordt gevormd uit het eigen adres van een instructie plus een gecodeerde offset, waardoor een locatie relatief ten opzichte van de code blijft.
Een auipc-reeks kan lokale gegevens bereiken, ook wanneer de hele groep code en gegevens wordt verplaatst.
Pointer
Een pointer is een waarde die een object- of codeadres voorstelt en wordt gebruikt om te bepalen wat een laad- of opslaginstructie benadert of waarheen een indirecte sprong gaat.
Een pointerregister levert een adres, terwijl een laadinstructie de waarde ophaalt die op dat adres is opgeslagen.
Positieonafhankelijke code
Positieonafhankelijke code gebruikt relatieve verwijzingen en indirectie zodat de code correct kan worden uitgevoerd op verschillende laadadressen.
Als een bibliotheek en haar lokale doelen samen worden verplaatst, blijven de afstanden behouden die haar lokale verwijzingen gebruiken.
Principe van het opgeslagen programma
Het principe van het opgeslagen programma stelt instructies voor als binaire waarden in het geheugen, zodat een processor ze kan ophalen en uitvoeren.
De programmateller levert het adres van waaruit de volgende gecodeerde instructie wordt opgehaald.
Programmateller
De programmateller is het register dat het adres bevat van de instructie die in de instructiereeks wordt opgehaald of uitgevoerd.
Een genomen voorwaardelijke sprong vervangt de opeenvolgende voortgang van de pc door het voor het doel berekende adres.
Proloog
Een proloog is de beginreeks die het frame van een procedure opzet en de binnengekomen registertoestand opslaat die de procedure moet bewaren.
Als je ra vóór een geneste aanroep opslaat, bescherm je het adres dat nodig is om terug te keren naar de buitenste aanroeper.
Pseudo-instructie
Een pseudo-instructie is een verkorte schrijfwijze voor de assembler die wordt uitgewerkt tot één of meer instructies die daadwerkelijk door de ISA van de processor zijn gedefinieerd.
Het aantal machine-instructies dat li produceert hangt af van de waarde van de constante operand.

R

Recursie
Recursie treedt op wanneer een procedure zichzelf direct of indirect aanroept, waardoor afzonderlijke actieve aanroepen met een eigen toestand ontstaan.
De faculteitsprocedure laadt het opgeslagen argument van een buitenste aanroep opnieuw nadat de binnenste aanroep het resultaat teruggeeft.
Registerspilling
Registerspilling is het opslaan van een nog benodigde registerwaarde in het geheugen zodat het register opnieuw kan worden gebruikt, en het opnieuw laden van de waarde wanneer die nodig is.
Extra nog benodigde waarden kunnen leiden tot opslag- en laadinstructies voor de stack, ook wanneer de oorspronkelijke C-code geen expliciete geheugentoegang heeft.
Registertoewijzing
Registertoewijzing is het toewijzen door de compiler van programmawaarden aan de beperkte registers die op elk moment tijdens de uitvoering beschikbaar zijn.
Een geoptimaliseerde lus kan de index en de opgebouwde som gedurende veel iteraties in registers bewaren.
Registerveld
Een registerveld is een deel met vaste breedte van een instructiecodering dat een bron- of doelregister met een nummer aanduidt.
Een 5-bitsveld stelt a0 voor als registernummer 10, binair geschreven als 01010.
Rekenkundige verschuiving naar rechts
Een rekenkundige verschuiving naar rechts verplaatst bits naar lagere posities en vult hoge posities met de oorspronkelijke tekenbit.
Als je −7 met teken 1 positie naar rechts schuift, krijg je −4 omdat het resultaat naar min oneindig wordt afgerond.
Relatieve relocatie
Relatieve relocatie is het aanpassen van een verwijzing op basis van de afstand tussen een geplaatste instructie of gegevenslocatie en het doel ervan.
Als slechts één sectie wordt verplaatst, kan een aanroepoffset veranderen, ook wanneer de instructiebytes aanvankelijk ongewijzigd waren.
Relocateerbaar objectbestand
Een relocateerbaar objectbestand bevat machinecode, gegevens en metadata waarvan de definitieve plaatsing en externe verwijzingen later kunnen worden afgehandeld.
Een objectbestand produceren met -c stopt vóór de combinatie van bibliotheken en modules die nodig is voor een uitvoerbaar bestand.
RISC
RISC is een aanpak voor instructiesetontwerp die de voorkeur geeft aan regelmatige coderingen, relatief eenvoudige bewerkingen en afzonderlijke instructies voor geheugenoverdracht.
Extensies toevoegen kan een RISC-ISA uitbreiden zonder de load-storeorganisatie weg te nemen.

S

Sprong met opslag van het terugkeeradres
Een sprong met opslag van het terugkeeradres draagt de besturing over en schrijft ook het adres van de volgende instructie naar een doelregister voor later gebruik.
jal ra, Helper legt vast waar de aanroeper moet hervatten wanneer Helper terugkeert.
Sprongoffset
Een sprongoffset is de afstand in bytes met teken vanaf het eigen adres van de voorwaardelijke spronginstructie tot het bedoelde doeladres.
Een achterwaarts doel geeft een negatieve offset, ook wanneer beide adressen positief zijn.
Sprongtabel
Een sprongtabel is een geïndexeerde verzameling codeadressen die wordt gebruikt om de besturing efficiënt tussen meerdere gevallen over te dragen.
Een keuze via een switch moet het case-bereik controleren voordat een adres uit de sprongtabel wordt gelezen.
Stackframe
Een stackframe is het gebied dat wordt gereserveerd voor de opgeslagen toestand, lokale objecten, naar het geheugen verplaatste waarden en stackargumentruimte van één procedureactivering.
Drie actieve aanroepen kunnen drie afzonderlijke kopieën van het opgeslagen argument van dezelfde functie bevatten.
Stackpointer
De stackpointer is het register dat de huidige grens van gereserveerde stackopslag aangeeft en beweegt wanneer frames worden gereserveerd of vrijgegeven.
Als je 16 van sp aftrekt, reserveer je volgens de RISC-V-conventie een uitgelijnd frame op lagere adressen.
Statisch linken
Statisch linken neemt vereiste objectcode van bibliotheken op in het uitvoerbare bestand, waardoor die opgenomen code deel wordt van het eigen bestand van het programma.
Een statisch bibliotheekbestand vervangen werkt de code niet bij die al naar een ouder uitvoerbaar bestand is gekopieerd.
Symbolentabel
Een symbolentabel legt benoemde definities en niet-opgeloste verwijzingen vast die worden gebruikt om instructies en gegevens tussen vertaalde modules te verbinden.
Een functie kan ongedefinieerd zijn in het ene objectbestand en gedefinieerd in een ander objectbestand dat aan de linker wordt geleverd.
Systeemaanroep
Een systeemaanroep is een verzoek van een programma om een dienst van het besturingssysteem via de interface die de uitvoeringsomgeving definieert.
Op RISC-V Linux vraagt ecall met 93 in a7 om afsluiting, met a0 voor de status.

T

Tekenuitbreiding
Tekenuitbreiding maakt een bitpatroon met teken breder door de hoogste bit naar de nieuwe hoge posities te kopiëren, waarbij de waarde met teken behouden blijft.
Het laden van byte 0xF0 met lb produceert 0xFFFFFFF0, de 32-bitsrepresentatie van −16.

U

Uitgestelde binding
Uitgestelde binding stelt het oplossen van een procedure uit een gedeelde bibliotheek uit tot de eerste aanroep en legt het resultaat vast voor latere aanroepen.
De eerste aanroep kan de dynamische linker starten, terwijl latere aanroepen een al opgeslagen adres gebruiken.

V

Veldoffset binnen een structuur
Een veldoffset binnen een structuur is de afstand in bytes vanaf het begin van een structuur tot een lid, bepaald door de ABI-indeling.
Een laadinstructie telt de veldoffset op bij het adres van het huidige object om het lid van dat object te bereiken.
Voorwaardelijke sprong
Een voorwaardelijke sprong verandert het adres van de volgende instructie alleen wanneer de vergelijking slaagt en gaat anders opeenvolgend verder.
bne t0, t1, Loop keert alleen terug naar Loop zolang de twee registerpatronen verschillen.

Over deze les

Door StudeliFout melden

Maak je eigen Studiemateriaal

Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.

Aan de slag