09 — Funksjoner, CALL/RET og ABI
Læringsmål
Etter denne leksjonen skal du kunne forklare hvordan CALL og RET bruker stacken, identifisere en returadresse, sende enkle argumenter etter EduCPU ABI, identifisere caller- og callee-saved registre og forklare hvorfor en calling convention er nødvendig.
Hvorfor funksjoner trenger regler
Et funksjonskall skaper flere spørsmål:
- Hvor fortsetter kjøringen etterpå?
- Hvor plasseres argumentene?
- Hvor plasseres returverdien?
- Hvilke registre kan funksjonen endre?
- Hvem gjenoppretter midlertidig stackplass?
En ABI (Application Binary Interface) svarer presist på disse spørsmålene.
EduCPU ABI v0 er bevisst liten slik at vi kan følge hver eneste byte.
CALL lagrer hvor vi skal returnere
Se på:
CALL add_one
HALT
add_one:
ADDI R0, 1
RET
Når CALL utføres, peker PC allerede på instruksjonen etter CALL. Denne adressen er returadressen.
EduCPU pusher den 16-bits returadressen på stacken og legger deretter funksjonsadressen i PC.
CALL pusher returadressen med high byte først og deretter low byte. Fordi stacken vokser nedover, ender low byte på MEM[SP].
RET gjør det motsatte: den popper low byte, så high byte, bygger den 16-bits adressen igjen og legger den i PC.
Hvordan vet EduCPU hvor den skal returnere?
Den forstår ikke funksjoner. CALL lagrer et tall — retur-PC — med et presist definert stackformat. RET leser bytene tilbake og legger den rekonstruerte adressen i PC.
Argumenter og returverdier
EduCPU ABI v0 sender opptil fire byte-store argumenter i registre:
| Formål | Register |
|---|---|
| argument 1 | R0 |
| argument 2 | R1 |
| argument 3 | R2 |
| argument 4 | R3 |
| primær 8-bits returverdi | R0 |
| high byte av 16-bits returverdi | R1 |
Eksempel:
MOVI R0, 20
MOVI R1, 22
CALL add
HALT
add:
ADD R0, R1
RET
Caller legger 20 og 22 i R0 og R1. Funksjonen returnerer 42 i R0.
Ingen spesiell hardware sier at «R0 er argument 1». Betydningen kommer fra ABI-kontrakten som caller, callee og compiler deler.
Caller-saved og callee-saved registre
ABI v0 definerer:
- R0–R3: argument-/returregistre og caller-saved;
- R4–R6: callee-saved;
- R7: caller-saved scratch;
- FLAGS: caller-saved.
Caller-saved betyr at caller må bevare en verdi dersom den trenger verdien etter funksjonskallet.
Callee-saved betyr at en funksjon som endrer registeret må gjenopprette den opprinnelige verdien før den returnerer.
Reglene gjør at funksjoner skrevet uavhengig av hverandre kan samarbeide.
Stackbalanse
En funksjon må returnere med SP balansert i forhold til tilstanden ved inngang, bortsett fra CALL/RET sin egen returadressemekanisme.
EduCPU har også ENTER n og LEAVE n for lokal stackplass:
worker:
ENTER 2
; to bytes lokal frame-plass
LEAVE 2
RET
ENTER 2 flytter SP to bytes ned. LEAVE 2 gjenoppretter den.
Compileren kan lese og skrive bytes i stack-framen med LOADS og STORES. Vi ser hvorfor dette er nyttig når vi følger EduC gjennom kodegenereringen.
Gjennomgått eksempel
Leksjonsfixturen kaller en funksjon med to argumenter:
MOVI R0, 20
MOVI R1, 22
CALL add
HALT
add:
ADD R0, R1
RET
Før kjøring, forutsi:
- adressen umiddelbart etter CALL;
- SP umiddelbart før CALL;
- de to minnebytene CALL skriver;
- SP ved inngangen til
add; - R0 etter ADD;
- PC og SP umiddelbart etter RET.
Kjør deretter steg for steg og sammenlign.
ABI kontra ISA
Dette skillet er viktig.
ISA-en definerer hva CALL, RET, PUSH, registre og minneoperasjoner gjør.
ABI-en definerer konvensjoner for hvordan disse mekanismene brukes sammen: argumentregistre, returregistre, saved-registre og stackdisiplin.
CPU-en håndhever ISA-en. Programmer og verktøy blir enige om å følge ABI-en.
Oppgaver
Forståelse
- Hva er en returadresse helt konkret?
- Hvilken byte av en EduCPU-returadresse ligger på
MEM[SP]? - Hvor sendes det første byte-argumentet?
- Hvilke registre er callee-saved?
- Hvorfor kan to funksjoner være uenige selv om begge bruker gyldige ISA-instruksjoner?
Praktisk
Skriv en funksjon double som tar imot én byte i R0 og returnerer det dobbelte i R0.
Skriv deretter en funksjon som endrer R4 midlertidig. Bevar R4 etter ABI-reglene.
Utforsk selv
Kjør CALL og RET steg for steg mens du følger PC, SP og minnet rundt stacken.
Legg deretter et nytt funksjonskall inne i den første funksjonen. Tegn begge returadressene på stacken før du kjører programmet.
Sjekk forståelsen
Følg kjeden:
caller → argumentregistre → CALL → returadresse på stack
→ callee → returverdi → RET → caller
Hvilke deler er hardware/ISA-oppførsel, og hvilke eksisterer bare fordi programvare er enig om ABI-en?
Verktøy
Bruk den ekte kursfixturen: course/examples/lesson09-call-ret-abi.eduasm.
EduVis: bruk nettleservisualiseringen for å stege gjennom CPU-tilstanden. eduguide: bruk den guidede PREDICT → OBSERVE → EXPLAIN-flyten.
Forvent viktige tilstands- eller compiler-endringer før du kjører fixturen, og sammenlign deretter med resultatet.
Neste
Nå kjenner vi nok av lavnivåmekanismene til å stille et mye større spørsmål: Hvordan kan programmereren skrive noe mer uttrykksfullt og la verktøy produsere alt dette automatisk? Neste: Hva er en compiler?