- Leren
- Informatica & ICT
- Informatica
- Computerarchitectuur: processors, geheugen en prestaties
- Instructiesets en assembleertaal
Instructiesets en assembleertaal
GemiddeldLes 2 van 6 in Cursus Computerarchitectuur: processors, geheugen en prestaties
Wat je leert
- 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 isBeschrijf 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 berekenenBenoem 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 opGebruik 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 operandenGebruik onmiddellijke operanden voor kleine constanten en bouw volledige 32-bitsconstanten en adressen op met lui, auipc, addi en de pseudo-instructies li en la.
- Leg uit wat een instructiesetarchitectuur is
- Gebruik rekenkundige, logische, schuif- en vergelijkingsinstructies en vertaal keuzestructuren en lussen uit C naar voorwaardelijke en onvoorwaardelijke sprongen.
- Gebruik rekenkundige, logische en schuifinstructiesReken 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 uitGebruik 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 assembleertaalZet if-else-instructies, samengestelde voorwaarden, while- en for-lussen en switch-instructies om in sprongen en volg de resulterende code met concrete waarden.
- Gebruik rekenkundige, logische en schuifinstructies
- 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 opslaginstructiesDeel 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 machinecodeCodeer 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.
- Codeer registerinstructies, instructies met onmiddellijke operanden en opslaginstructies
- 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 aanroepconventieGeef 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 proceduresReserveer 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 assembleertaalZet dereferenties en rekenkundige bewerkingen met pointers, structuurvelden, arrayparameters en tekenreeksen met een afsluitende nulbyte om naar laad- en opslaginstructies en adresberekeningen.
- Roep procedures aan en keer eruit terug volgens de aanroepconventie
- 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 makenBeschrijf 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 startenBeschrijf 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.
- Leg uit hoe compilers en assemblers objectbestanden maken
- Vergelijk RISC-V met complexe instructiesets zoals x86 en beoordeel afwegingen bij instructiesetontwerpen voor prestaties, codeomvang en compatibiliteit.
- Vergelijk de ontwerpkenmerken van RISC en CISCBeschrijf 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 compatibiliteitGebruik 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.
- Vergelijk de ontwerpkenmerken van RISC en CISC
Instructiesets en assembleertaal: uitleg
1Instructies, 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-bitspatronen zijn. Een kleine registerbank beperkt ook de toegangskosten en de ruimte die registervelden in instructies innemen.
| Registers | ABI-namen | Gebruikelijk doel |
|---|---|---|
| x0 | zero | Geeft bij lezen altijd 0 |
| x1, x2 | ra, sp | Terugkeeradres en stackpointer |
| x3, x4 | gp, tp | Globale pointer en threadpointer |
| x5–x7, x28–x31 | t0–t6 | Tijdelijke waarden |
| x8–x9, x18–x27 | s0–s11 | Waarden die bij aanroepen behouden blijven |
| x10–x17 | a0–a7 | Argumenten; 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:
| Tekst | Basisinstructie of uitwerking |
|---|---|
mv rd, rs | addi rd, rs, 0 |
nop | addi x0, x0, 0 |
not rd, rs | xori rd, rs, -1 |
neg rd, rs | sub rd, x0, rs |
li rd, c, voor een kleine c | addi 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.
2Rekenkundige 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
| Instructie | Sprongvoorwaarde |
|---|---|
beq, bne | Gelijk, ongelijk |
blt, bge | Kleiner dan met teken, groter dan of gelijk met teken |
bltu, bgeu | Kleiner 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.
3Instructievelden 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:
| Offsetbits | Instructiebits |
|---|---|
| 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.
4Procedureaanroepen 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.
5Van 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.
5.2 Link modules en start een proces
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.
6Afwegingen 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
1Volg operanden van een expressie naar het geheugen
15 stappen
- 1IntroductieVolg 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.
- 2UitlegLees de instructieovereenkomst
De ISA definieert wat
add a0, a1, a2moet 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. - 3UitdagingTel instructies na het assembleren
Een assembler werkt één
liuit tot twee machine-instructies, gevolgd door éénadd. 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.
- 4VoorbeeldBewaar de tussensom
Bereken
r = (u + v) - wmetu = 18,v = 7,w = 6ina1,a2,a3:add t0, a1, a2 sub a0, t0, a3Na de eerste instructie bevat
t025. Na de tweede bevata019. De drie invoerwaarden blijven beschikbaar omdat geen van beide instructies naar hun registers schrijft.t0heeft één doel: het eerste resultaat bewaren totdat de aftrekking het leest. - 5UitdagingVerander het doel, behoud de bronnen
Met dezelfde invoerwaarden wordt na de optelling nu
sub a3, t0, a3uitgevoerd. Wat verandert er ten opzichte van het schrijven van het resultaat naara0?---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.a0blijft ongewijzigd, dus een aanroeper die daar het resultaat verwacht, ontvangt dit nieuwe resultaat niet. - 6UitlegZet een index om in een adres
Een array van gehele getallen begint op het adres in
s1. Elke RV32 ILP32-intneemt 4 bytes in, dus element 2 begint 8 bytes na de basis en element 5 begint 20 bytes erna. De laadinstructielw t0, 8(s1)leest de waarde van element 2; ze zet niet het adres zelf int0. Het basisregister blijft ongewijzigd. - 7VoorbeeldLaden, berekenen, opslaan
Implementeer
A[5] = A[2] + rmetr = 19ina0: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 naarA[5]. Geheugen lezen en schrijven vereist afzonderlijke instructies rond de rekenkundige bewerking. De waarde vanA[2]blijft 11. - 8UitdagingHerken de verkeerde eenheid in een offset
Iemand verandert de eerste regel in
lw t0, 2(s1)omdat de expressieA[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.
- 9VoorbeeldLees een byte uit een opgeslagen woord
Sla
0x7193D2A6op een uitgelijnd adres op een little-endianmachine op. De bytes in oplopende adresvolgorde zijn0xA6,0xD2,0x93,0x71. Een bytelaadinstructie op offset 1 selecteert0xD2.lbulevert 210 op, terwijllb−46 oplevert door bit 7 uit te breiden naar de hoge registerposities. De geheugenbyte is identiek; de laadinstructie bepaalt de interpretatie. - 10UitlegBepaal het lage deel met teken
Bouw nu
0x00003D20op in een register. De onderste 12 bits zijn0xD20, die als 12-bits onmiddellijke operand met teken −736 voorstellen. De bovenste stap moet daarom één blok van 4096 bytes hoger beginnen dan de zichtbare0x3doet vermoeden. Als je0x4kiest, krijg je0x4000, waarvan je0x2E0aftrekt om de gewenste waarde te bereiken. - 11VoorbeeldBouw en controleer de constante
Gebruik:
lui t1, 0x4 addi t1, t1, -736De 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. Metli t1, 0x3D20laat je de assembler de opbouw kiezen, maar de uitwerking neemt nog steeds echte instructies in beslag. - 12UitdagingHerstel een opgesplitste constante
Een opsplitsing gebruikt
lui t1, 0x3enaddi t1, t1, -736. Hoe ver ligt het resultaat van0x3D20af en welk veld moet worden hersteld?---reveal---
Het resultaat is
0x2D20, precies 4096 te klein. Verhoog het hoge veld van0x3naar0x4en behoud de lage term met teken. De term veranderen in positief0xD20zou het positieve bereik met teken vanaddioverschrijden. - 13TipVolg 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.
- 14UitdagingCombineer de stappen voor adres en waarde
Met arraybasis
s1is elementA[6]gelijk aan 17.t1bevat de correct opgebouwde constante0x3D20. Wat verandertlw 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 inA[1]. De twee offsets tellen bytes, terwijl de constante een rekenkundige waarde is. Het oorspronkelijke element op index 6 en het basisadres ins1blijven ongewijzigd. - 15SamenvattingVolg 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.
2Volg berekeningen naar keuzestructuren en lussen
14 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieMaak het uitvoeringspad zichtbaar
- 2VoorbeeldSchuif het veld op zijn plaats
- 3VoorbeeldBehoud alleen de breedte van het veld
- 4UitdagingVoorspel een verschuiving met teken
- 5UitlegVergelijk met de juiste interpretatie
- 6VoorbeeldOnderscheid een sprong van een aanroep
- 7UitdagingTest één patroon op twee manieren
- 8VoorbeeldTeken de twee paden van een if
- 9UitlegControleer de grens vóór het laden
- 10VoorbeeldVolg drie uitvoeringen van de lusinhoud
- 11UitdagingTel het volledige uitgevoerde pad
- 12VerbandVervang herhaald schalen door een opschuivend adres
- 13UitdagingBescherm een laadinstructie met kortsluitevaluatie
- 14SamenvattingControleer waarden en paden samen
3Codeer een instructie en reconstrueer een voorwaardelijke sprong
12 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVertaal in beide richtingen
- 2UitlegBepaal het formaat en de operanden
- 3VoorbeeldSchrijf de velden op volledige breedte
- 4VoorbeeldZet het woord om en decodeer het
- 5UitdagingBepaal het bronregister van een opslaginstructie
- 6UitlegMeet de achterwaartse afstand
- 7VoorbeeldSplits de onmiddellijke operand van de sprong
- 8VoorbeeldStel de volledige sprong samen
- 9UitdagingVul de ontbrekende nulbit weer aan
- 10VerbandBehoud de relatieve afstand als code verschuift
- 11UitdagingGebruik de opcode om een foutieve decodering af te wijzen
- 12SamenvattingControleer een machinewoord in omgekeerde richting
4Behoud waarden tijdens geneste aanroepen
15 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieBescherm wat de aanroeper nog nodig heeft
- 2VoorbeeldSpecificeer het resultaat van de wrapper
- 3UitlegPas de bewaarafspraken toe
- 4UitdagingKies de toestand die de aanroep moet overleven
- 5VoorbeeldReserveer en vul het frame
- 6VoorbeeldCombineer resultaten voordat je opslagruimte vrijgeeft
- 7UitlegHerhaal het patroon recursief
- 8UitdagingDoorloop de opgeslagen argumenten bij terugkeer
- 9UitlegOnderscheid een adres van het object
- 10VoorbeeldKies bytebewerkingen voor een tekenreeks
- 11UitdagingEindig op de juiste adressen
- 12TipHerstel de opgeslagen waarde, niet het nieuwe resultaat
- 13VerbandDe levensduur van een object volgt het frame
- 14UitdagingBehoud een doelpointer tijdens een hulpprocedure
- 15SamenvattingControleer een volledige aanroep
5Volg een aanroep van broncode tot opstarten
11 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieBepaal wat elk hulpmiddel weet
- 2UitlegCompileer voor een doel-ISA
- 3VoorbeeldLaat de externe aanroep onafgemaakt
- 4UitdagingOnderscheid het genereren van gegevens van de uitvoering
- 5VoorbeeldPlaats de doelmodule
- 6VoorbeeldPas de relatieve verwijzing aan
- 7UitlegKies wanneer bibliotheekcode wordt geleverd
- 8UitdagingBepaal in welke stap de fout optreedt
- 9VerbandMaak een proces voordat je main aanroept
- 10UitdagingLeg uit waarom een objectbestand niet het voltooide programma is
- 11SamenvattingLeid een nieuwe fout terug naar de juiste stap
6Vergelijk ontwerpen met de juiste maat
10 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieHoud drie metingen gescheiden
- 2VoorbeeldVergelijk de zichtbare geheugenbewerking
- 3UitlegVerbind codering met de ontwerpkeuze
- 4UitdagingDeel een kenmerk zorgvuldig in
- 5VoorbeeldBereken de eerste uitvoeringstijd
- 6VoorbeeldVergelijk een groter aantal instructies
- 7UitdagingOnderscheid bytebesparing van versnellingsfactor
- 8VerbandCompatibiliteit gaat verder dan de ISA
- 9UitdagingBeoordeel een bewering over optimalisatie in assembleertaal
- 10SamenvattingNoem het bewijs achter de vergelijking
Instructiesets en assembleertaal: oefenvragen
Set 1: Registers, geheugen en constanten10 Oefenvragen
Vraag 1 van 10
Toon antwoordVerberg antwoord
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
sub a2, a0, a1 bevat a0 de waarde 28 en a1 de waarde 9. Welke waarde wordt naar a2 geschreven?Toon antwoordVerberg antwoord
a2. Als je de bronnen omwisselt, krijg je −19, maar dat is hier niet hun volgorde.Vraag 3 van 10
A[7]?Toon antwoordVerberg antwoord
Vraag 4 van 10
sw de waarde 0x86C4217D op op 0x3000. Welke waarde levert lbu t0, 2(s0) op wanneer s0 = 0x3000?Toon antwoordVerberg antwoord
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
lui en welke onmiddellijke operand met teken voor addi bouwen 0x00002ABC op in RV32?Toon antwoordVerberg antwoord
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
t3, roept een hulpprocedure aan die de conventie volgt en heeft daarna de oorspronkelijke waarde nodig. Wat moet de aanroeper regelen?Toon antwoordVerberg antwoord
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
auipc t0, 0x2 uitgevoerd op 0x4000, gevolgd door addi t0, t0, -24. Welke uiteindelijke waarde staat in t0?Toon antwoordVerberg antwoord
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
0xB2 wordt eenmaal geladen met lb en eenmaal met lbu. Welke decimale resultaten leveren de twee laadinstructies in die volgorde op?Toon antwoordVerberg antwoord
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
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?Toon antwoordVerberg antwoord
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)
Toon antwoordVerberg antwoord
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
add 1 optelt bij 0xFFFFFFFF?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
bgt a2, a3, Done?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
srli t0, a0, 8 en andi t0, t0, 0x1F in t0 als a0 = 0x0000D6B9?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
a0 = -11 met teken respectievelijk srai t0, a0, 2 en de gehele deling -11 / 4 in C op?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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
while (A[i] == 6) i++; begint met i = 1 en A = {6, 6, 6, 4, 6}. Wat is i wanneer de lus eindigt?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)
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
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?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
sw a2, 20(sp) het veld rs1?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
addi t2, s1, -36?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
0x308C heeft 0x3068 als doel. Welke byteoffset met teken wordt gecodeerd?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
jal op 0x1200 heeft offset +84 met teken en doelregister ra. Welk sprongdoel en terugkeeradres ontstaan?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
addi t2, s1, -36 met velden van het I-type? Hierbij zijn t2 = x7, s1 = x9, de ADDI-opcode is 0x13 en funct3 = 000.Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
0xFEC12623. De opcode 0x23 selecteert een opslaginstructie en funct3 = 010 selecteert een woord. Welke assembleerinstructie stelt dit voor?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
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.Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
0x30 en 0x54. De sectie verhuist van 0x9000 naar 0xB000 zonder de interne indeling te veranderen. Welke sprongoffset en welk doeladres ontstaan?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
s4. Welke waarde moet s4 bevatten wanneer de procedure terugkeert?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
a0 in RV32 ILP32 naar het volgende element wijzen wanneer het een int * bevat?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
sp = 0x8000, reserveert 32 bytes en voert daarna sw ra, 28(sp) uit. Op welk adres begint het opgeslagen woord?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
ra en s2 op te slaan, elk één woord. Wat is de kleinste positieve framegrootte die de vereiste stackuitlijning behoudt?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
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?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
gcc -c module.c zonder te linken?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
filter uit een bibliotheek. Daarna wordt het bibliotheekarchief gecorrigeerd, maar het uitvoerbare bestand blijft ongewijzigd. Welke code bevat het uitvoerbare bestand voor filter?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
a7, 7 in a0 en voert daarna ecall uit. Welke dienst wordt aangevraagd?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
main. Wat verklaart de fout en wat zou anders zijn voor code uit die bibliotheek die statisch is opgenomen?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
add eax, [ebx+12] uitdrukt, waarbij eax overeenkomt met a0 en ebx met s1?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
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?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
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Kom je niet uit je eigen opgave? Huiswerkhulp
Kernbegrippen
A
Aanroepconventie
a0 tot en met a7.Adres laden
la t0, Item is een afzonderlijke laadinstructie nodig om de waarde op Item te lezen.Adressering met geschaalde index
Afsluitende nulbyte
Assembleertaal
add a0, a1, a2 benoemt een bewerking met een gedefinieerde RISC-V-machinecodecodering.Assembler
call uit en kan relocatie-items achterlaten voor een functie die in een andere module is gedefinieerd.Assemblerdirectief
.word 5 genereert een waarde van 4 bytes, terwijl .text selecteert waar de daaropvolgende code thuishoort.B
Basis-plus-offsetadressering
lw t0, 20(s1) begint het geladen woord 20 bytes voorbij het adres dat in s1 is opgeslagen.Basisblok
Binaire applicatie-interface
Binaire compatibiliteit
Bitmasker
0xFF behoudt de onderste byte en zet alle hogere registerposities op nul.Bladprocedure
Byteadressering
C
CISC
Codedichtheid
Compiler
Conditievlaggen
Cycli per instructie
D
Dereferentie
lw t0, 0(a0) leest een geheel getal van 4 bytes via het adres dat op dat moment in a0 staat.Disassemblage
Door de aangeroepen procedure te bewaren register
s1 intern gebruiken als ze vóór terugkeer de oorspronkelijke s1 van de aanroeper herstelt.Door de aanroeper te bewaren register
t2 moet worden opgeslagen vóór een hulpprocedure die dat register mag gebruiken.Doorgaan naar de volgende instructie
Dynamisch aantal uitgevoerde instructies
Dynamisch linken
E
Elementstapgrootte
Epiloog
sp wordt verhoogd, blijven hun offsets relatief aan het gereserveerde frame.F
Functieveld
funct7-bits.G
Gecomprimeerde instructie
I
Impliciete bit
Indirecte sprong
jalr kan een aanroep via een functiepointer, een terugkeer of een keuze via een sprongtabel implementeren.Instellen bij kleiner dan
slt en sltu kunnen verschillen wanneer de hoogste bit van een bron de bron verschillende waarden met en zonder teken geeft.Instructieformaat
Instructiesetarchitectuur
Integerregister
J
Just-in-timecompilatie
K
Kortsluitevaluatie
&& voorkomt in C dat de tweede operand wordt geëvalueerd.L
Levensduur van een object
Linker
Little-endianbytevolgorde
0x12345678 plaatst 0x78 in de eerste byte en 0x12 in de vierde.Load-storearchitectuur
Loader
Logische verschuiving naar rechts
Lusgrens
M
Micro-operatie
Microarchitectuur
N
Natuurlijke uitlijning
Nibble
Nulregister
zero schrijven als het programma geen terugkeeradres hoeft op te slaan.Nuluitbreiding
0xF0 met lbu produceert de positieve waarde 240 in een 32-bitsregister.O
Onmiddellijke operand
addi t0, t0, 1 is als onmiddellijke operand in de instructie gecodeerd.Onmiddellijke operand voor de hoge bits
lui boven 12 lage nullen plaatst bij het opbouwen van een RV32-registerwaarde.addi kan een laag deel met teken leveren om een volledige 32-bitsconstante te vormen.Opcode
Overloop
P
PC-relatief adres
auipc-reeks kan lokale gegevens bereiken, ook wanneer de hele groep code en gegevens wordt verplaatst.Pointer
Positieonafhankelijke code
Principe van het opgeslagen programma
Programmateller
Proloog
ra vóór een geneste aanroep opslaat, bescherm je het adres dat nodig is om terug te keren naar de buitenste aanroeper.Pseudo-instructie
li produceert hangt af van de waarde van de constante operand.R
Recursie
Registerspilling
Registertoewijzing
Registerveld
a0 voor als registernummer 10, binair geschreven als 01010.Rekenkundige verschuiving naar rechts
Relatieve relocatie
Relocateerbaar objectbestand
-c stopt vóór de combinatie van bibliotheken en modules die nodig is voor een uitvoerbaar bestand.RISC
S
Sprong met opslag van het terugkeeradres
jal ra, Helper legt vast waar de aanroeper moet hervatten wanneer Helper terugkeert.Sprongoffset
Sprongtabel
Stackframe
Stackpointer
sp aftrekt, reserveer je volgens de RISC-V-conventie een uitgelijnd frame op lagere adressen.Statisch linken
Symbolentabel
Systeemaanroep
ecall met 93 in a7 om afsluiting, met a0 voor de status.T
Tekenuitbreiding
0xF0 met lb produceert 0xFFFFFFF0, de 32-bitsrepresentatie van −16.U
Uitgestelde binding
V
Veldoffset binnen een structuur
Voorwaardelijke sprong
bne t0, t1, Loop keert alleen terug naar Loop zolang de twee registerpatronen verschillen.- 1Computerprestaties
- 2Instructiesets en assembleertaal
- 3Rekenen met computers
- 4Datapad en pipelining
- 5Geheugenhiërarchie en caches
- 6Parallelle processors
Over deze les
Door StudeliFout melden
Maak je eigen Studiemateriaal
Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.