12 — EduIR og kodegenerering

Læringsmål

Etter denne leksjonen skal du kunne forklare hvorfor en compiler bruker en mellomrepresentasjon, lese enkel EduIR, skille lowering fra kodegenerering og følge EduIR-verdier til stack slots, registre og EduASM.

Hvorfor enda en representasjon?

Etter parsing og semantisk analyse har compileren et kontrollert høynivåprogram. Den kunne forsøkt å generere maskininstruksjoner direkte, men da ville flere forskjellige jobber blitt blandet sammen.

EduCPU senker derfor programmet til EduIR.

EduIR er en liten, pedagogisk, non-SSA mellomrepresentasjon. Den gjør operasjoner eksplisitte, men er fortsatt uavhengig av fysiske EduCPU-registre og instruksjonskoding.

Kjeden er:

kontrollert EduC-AST
      ↓ lowering
EduIR
      ↓ kodegenerering
EduASM

Skillet lar oss undersøke hva programmet gjør før vi bestemmer hvordan EduCPU skal gjøre det.

Kildevariabler og midlertidige verdier

Se på:

byte main() {
    byte x = 40;
    return x + 2;
}

En forenklet EduIR-lignende visning er:

const %t0, 40
copy x, %t0
const %t1, 2
add %t2, x, %t1
ret %t2

Midlertidige navn som %t0 er verdier compileren lager. x er en variabel fra kildekoden.

Den nøyaktige IR-en som verktøyet produserer er autoritativ; denne forenklede visningen brukes for å lære rollene til operasjonene.

Hvordan vet EduCPU hva %t2 betyr?

Det gjør den aldri. EduIR eksisterer bare inne i compileren. Kodegenereringen må velge konkret lagring og konkrete instruksjoner før CPU-en ser noe.

Kontrollflyt blir eksplisitt

Høynivåkonstruksjoner blir også eksplisitte.

En EduC-if kan senkes til labels, et betinget hopp og jumps. En while blir labels og en kontrollflytkant bakover.

Dette er nyttig fordi backenden ikke lenger trenger å forstå hele syntaksen til en EduC-while. Den trenger bare å oversette et lite sett IR-operasjoner.

Kodegenerering velger maskinressurser

EduCPU v0-backenden bruker en bevisst enkel strategi: parametere, kildevariabler og compiler-temporaries får hver sin én-byte stack slot.

Den genererte funksjonen får derfor en frame:

ENTER n
...
LEAVE n
RET

Verdier flyttes mellom stack slots og scratch-registre mens operasjonene utføres.

Dette er ikke ment som en optimaliserende register allocator. Det er ment å være forutsigbart og pedagogisk.

Fra IR-addisjon til EduASM

Anta at EduIR skal addere to verdier.

Backenden kan måtte:

  1. laste én verdi fra dens stack slot;
  2. laste den andre i et annet register;
  3. utføre ADD;
  4. lagre resultatet i destination-slotten.

Én konseptuell IR-operasjon kan altså kreve flere maskininstruksjoner.

Det gir et viktig compilerpoeng:

én kildeoperasjon ≠ én IR-operasjon ≠ én maskininstruksjon

Funksjonskall møter ABI-en

Ved et kall må kodegenereringen gjøre abstrakte argumenter om til ABI-reglene fra leksjon 09.

Argumentene lastes i R0–R3, CALL overfører kontrollen og returverdien kommer i R0. Hvis IR-en trenger resultatet senere, lagrer backenden det i stack slotten som tilhører destination-verdien.

ABI-en er dermed kontrakten mellom compiler-generert caller- og callee-kode.

Gjennomgått eksempel

Den CI-testede kilden er course/examples/lesson12-ir-codegen.educ:

byte add_two(byte x) {
    byte y = x + 2;
    return y;
}

byte main() {
    return add_two(40);
}

Kurstesten kjører den ekte pipelinen gjennom:

parse → semantisk analyse → EduIR → EduASM → objekt → link → kjøring

Den kontrollerer at EduIR inneholder funksjonene og aritmetikk-/call-operasjonene, at generert assembly inneholder frame- og call-mekanikk, og at sluttprogrammet returnerer 42.

Undersøk stegene

For fixturen, sammenlign:

EduC

Navn og strukturerte uttrykk er praktiske for mennesker.

EduIR

Operasjoner, verdier og kontrollflyt er eksplisitte, men det finnes ingen fysiske stackadresser eller kodede opcodes.

EduASM

ABI, registre, stack-frame-instruksjoner og konkrete kontrolloverføringer er synlige.

Maskinkode

Navn som x, %t0 og add_two trenger ikke lenger eksistere som kildespråkbegreper.

Hvert steg svarer på et forskjellig teknisk spørsmål.

Oppgaver

Forståelse

  1. Hvorfor ikke generere maskinkode direkte fra AST-en?
  2. Hva er forskjellen mellom en kildevariabel og en EduIR-temporary?
  3. Kjører EduCPU noen gang EduIR?
  4. Hvorfor kan én IR-operasjon kreve flere instruksjoner?
  5. Hvor blir ABI-en først konkret i denne pipelinen?

Praktisk

Endre x + 2 til x + 3. Forutsi hvilke høynivåsteg som forblir strukturelt like og hvilken generert konstantbyte som må endres.

Legg til enda en lokal variabel. Undersøk hvordan den genererte stack-framen endrer seg.

Utforsk selv

Skriv en EduC-if eller while. Sammenlign den strukturerte AST-en med eksplisitte EduIR-labels og branches, og deretter med EduASM-hoppinstruksjonene.

Finn én compiler-generert temporary og følg den fra EduIR til stack slotten som generert kode bruker.

Sjekk forståelsen

Forklar grensen:

EduIR: hvilke operasjoner og verdier som trengs
EduASM: hvordan EduCPU-ressurser utfører dem

Hvorfor er dette skillet nyttig både for compilerdesign og undervisning?

Representasjonssjekk

Velg én verdi, for eksempel resultatet av x + 2, og følg den gjennom tre nivåer:

EduIR temporary → stack slot/register → EduASM instruction sequence

Før du viser generert kode, forutsi hvilke maskinressurser som må brukes. Deretter sammenligner du med den faktiske backend-outputen.

Poenget er ikke å gjette nøyaktig kodegenerering, men å kunne forklare hvorfor den genererte koden trenger de ressursene den bruker.

Verktøy

Bruk den ekte kursfixturen: course/examples/lesson12-ir-codegen.educ.

EduGuide: bruk den guidede PREDICT → OBSERVE → EXPLAIN-flyten. For CPU-kjøring kan du bruke EduVis.

Forvent viktige tilstands- eller compiler-endringer før du kjører fixturen, og sammenlign deretter med resultatet.

Neste

Neste leksjon følger generert assembly inn i objektfiler, relocations og linking, der separat oversatte deler til slutt får konkrete adresser.