240 likes | 364 Views
CAP.5 LA PRODUZIONE DI CODICE. 5.1 I languaggi intermedi 5.2 Le instruzioni di assegnamento 5.3 I collegamenti e le espressioni booleane 5.4 La ripresa indietro. 5.1 I linguaggi intermedi. analizzatore sintattico. controllatore semantico. produttore di codice intermedio. produttore
E N D
CAP.5 LA PRODUZIONE DI CODICE • 5.1 I languaggi intermedi • 5.2 Le instruzioni di assegnamento • 5.3 I collegamenti e le espressioni booleane • 5.4 La ripresa indietro
5.1 I linguaggi intermedi analizzatore sintattico controllatore semantico produttore di codice intermedio produttore di codice Vantaggi: separare la parte frontale dalla parte finale, portabilità migliorata, facilità di ottimizazzione. Rappresentazione di un programma con un albero astratto o con un grafo aciclico orientato. Qui, questi alberi saranno rappresentati da un pseudo-codice, il codice con tre indirizzi, ben adattato alle strutture di controllo embricate e alle espressioni algebriche. Codice di bassissimo livello.
Istruzioni x <- y op z dove x, y et z sono costanti o nomi espliciti o prodotti dal compilatore stesso (temporanei) e op è un operatore qualsiasi (rappresentato da _nome). Esempio: a := b * - c + b * - c è rappresentato da : t1 <- _opp c t2 <- b _mul t1 t3 <- _opp c t4 <- b _mul t3 t5 <- t2 _plu t4 a <- t5 t1 <- _opp c t2 <- b _mul t1 t3 <- t2 _plu t2 a <- t3 Grafo aciclico, ottimizzato Albero astratto, non ottimizzato Temporari = nodi interni dell’albero o del grafo aciclico.
Varie istruzioni, con etichette simboliche se necessario, per controllare il flusso. • Assegnamento x <- y op z • Assegnamento x <- op y • Copia x <- y • Collegamento incondizionale goto L • Collegamento condizionale if x oprel y goto L • Puntatori e indirizzi x <- &y et x <- *y et *x <- y • Etichette etiq (niente operazione) Vediamo come produrre codice a tre indirizzi con un traduttore diretto dalla sintassi.
Qui, || disegna la concatenazione, NewTemp fabbrica una nuova variabile temporanea e Print stampa suoi argomenti. Regola Azione EE +E E.place := NewTemp ; E.code := E1 .code || E2 .code || Print (E . place ‘<-’ E1 . place ‘_plu’ E2 . place ) EE *E E.place := NewTemp ; E.code := E1 .code || E2 .code || Print (E . place ‘<-’ E1 . place ‘_mul’ E2 . place ) E– E E.place := NewTemp ; E.code := E1 .code || Print (E . place ‘<-’ ‘_opp’ E1 . place ) E (E ) E.place := E1 .place ; E.code := E1 .code E idE.place := id.place ; E.code :=‘ ’
5.2 Le istruzioni di assegnamento Interazione con la tabella dei simboli. Nelle istruzioni, invece di utilizzare i nomi, si ricerca nella tabella dei simboli se questo nome già esiste. Se è il caso, viene ritornato suo indice. Si no, un errore è segnalato. Qui segue una parte della grammatica attribuita per gli assegnamenti : I id:=E { p := Look ( id.name ) ; if p <> nil then I.code := E .code || Print ( id.place ‘<-’ E.place ) else error } EE +E { E.place := NewTemp ; E.code := E1 .code || E2 .code || Print ( E . place ‘<-’ E1 . place ‘_plu’ E2 . place )} E (E ) { E.place := E1 .place ; E.code := E1 .code } E id{ p := Look ( id.name ) ; if p <> nil then begin E.place := id.place ; E.code :=‘ ’ end else error }
Ottimizazzione dell’uso delle variabili temporanee. Si possono reutilizzare variabili temporanee. Infatti, queste sono "liberate" dopo la loro uso come argomento di una operazione. Si può dunque modificare la funzione NewTemp per ritornare la prima variabile temporanea libera. Assegnamento degli elementi delle tabelle Supponiamo che le tabelle abbiano una sola dimensione, con indici da 0 fino a n– 1. Una tabella è dichiarata come T[n].
Due tipi di costruzione utilizzano elementi di tabelle: Eid[E] dove in elemento di una tabella è utilizzato invece di un identificatore in un’espressione, e Iid[E]:=E dove si realizza un assegnamento a un elemento di una tabella. Infatti, l’indice indica solo uno spostamento dell’indirizzo in memoria. T[5] è dunque sinonimo di *(T+5)(o, più esattamente trasformato). Questo è realizzato con Eid[E] {p := NewTemp ; E.place := NewTemp ; E.code := E1 .code|| Print ( p ‘<-’ id.place ‘_plu’ E1 . place ) || Print ( E.place ‘<- *’ p ) } I id[E ]:= E {p := NewTemp ; I.code := E1 .code|| E2 .code || Print ( p ‘<-’ id.place ‘_plu’ E1 . place ) || Print (‘*’ p ‘<-’ E2 . place ) } (Le verificazioni della presenza nella tabella dei simboli sono state omesse)
Esempio: Supponiamo che T e U siano due tabelle, l’istruzione T[a + b] := U[b + T[c]] produce il codice: t1 <- a _plu b t2 <- T _plu c t3 <- *t2 t4 <- b _plu t3 t5 <- U _plu t4 t6 <- *t5 t7 <- T _plu t1 *t7 <- t6
5.3 I collegamenti e le espressioni booleane Grammatica utilizzata per espressioni booleane: EE orE | E andE | notE | ( E ) | EoprelE | true | false Convenzione numerica (1 = true, 0 = false) Supponiamo che abbiamo operazioni corrispondenti nel codice a tre indirizzi (_or _and _note per operatori relazionali come _equ). La valutazione delle espressioni booleane non presenta differenze con quella delle espressioni algebriche (Se si vuole una distinzione tra numeri e valori logici, è meglio usare un’altro simbolo invece del E). Abbiamo bisogno di un construttore di etichette simboliche NewLabel.
Azioni per un confronto. Regola E E > E Azioni p := NewLabel ; q := NewLabel ; E.place := NewTemp ; E.code := E1 . code || E2 . code || Print ( ‘if’ E1 . place ‘_leq’ E2 . place ‘goto’ p ) || Print ( E.place ‘<- 1’) || Print ( ‘goto’ q ) || Print ( p ) Print ( E.place ‘<- 0’) || Print ( q )
Azioni per un while. Regola I while E do I Azioni p := NewLabel ; q := NewLabel ; I.code := Print ( p )|| E .code || Print ( ‘if’E . place ‘_equ’ ‘0’ ‘goto’ q ) || I1 . code || Print ( ‘goto’ p ) || Print ( q )
Azioni per un if...then Regola IifE thenI Azioni p := NewLabel ; I.code := E .code || Print ( ‘if’E . place ‘_equ’ ‘0’ ‘goto’ p ) || I1 . code || Print ( p )
Azioni per un if...then..else Regola IifE thenI elseI Azioni p := NewLabel ; q := NewLabel ; I.code := E .code || Print ( ‘if’E . place ‘_equ’ ‘0’ ‘goto’ p ) || I1 . code || Print ( ‘goto’ q ) || Print ( p ) || I2 . code || Print ( q )
Esempio while a > b do if c > d then a := 1 else b := 0 produce il codice: etiq7 if a _leq b goto etiq1 t1 <- 1 goto etiq2 etiq1 t1 <- 0 etiq2 if t1 _equ 0 goto etiq8 if c _leq d goto etiq3 t2 <- 1 goto etiq4 etiq3 t2 <- 0 etiq4 if t2 _equ 0 goto etiq5 a <- 1 goto etiq6 etiq5 b <- 0 etiq6 goto etiq7 etiq8 Questo è un poco pesante (grande numero di etichette). Si può ottimizzare il codice.
Certe volte si utilizza un metodo di valutazione pigra delle congiunzioni o disgiunzioni. In questa, se il valore dell’espressione non necessita il calcolo del secondo termine dell’espressione, non viene calcolata. EE landE Azioni p := NewLabel ; q := NewLabel ; E.code := E1 .code || Print ( ‘if’ E1 . place ‘_equ’ ‘0’ ‘goto’ p ) || E2 . code || Print ( ‘if’ E2 . place ‘_equ’ ‘0’ ‘goto’ p ) || Print ( E.place ‘<- 1’) || Print ( ‘goto’ q ) || Print ( p ) || Print ( E.place ‘<- 0’) || Print ( q )
5.4 La ripresa indietro L’ottimizzazione delle etichette può essere realizzato in un secondo passo sul codice a tre indirizzi prodotto. Si può anche evitare completamente l’uso di etichette simboliche colla tecnica di ripresa indietro. Come si vede nell’esempio precedente, le etichette non sono create secondo l’ordine nel quale sono utilizzate nel codice. Certi collegamenti devono di più essere fatti verso istruzioni non ancora scritte. Nella tecnica di ripresa indietro, i collegamenti sono lasciati provvisoriamente indeterminati e vengono completati quando si produce il codice dell’istruzione di destinazione. Questi collegamenti sono gestiti come liste di numeri di istruzioni. (supponiamo qui che ogni istruzione ha un numero proprio).
Questo permette anche di non scrivere i pezzi di codice in modo separato, ma direttamente su una fila, lasciando però righe di goto incomplete. Per gestire le liste, abbiamo • CreateList ( i ) fabbrica una nuova lista che contiene i e ritorna un puntatore verso essa; • Fusion ( p1, p2 ) concatena due liste e ritorna un puntatore; • Back ( p, i ) inserisce i come etichetta in tutte le istruzioni della lista p, poi libera lo spazio occupato da p. • Una variabile globale numero contiene il numero della prossima istruzione da produrre. Un altro vantaggio è che la grammatica prodotta è sempre S-attribuita. Ogni espressione booleana ha due attributi E.true e E.false che sono liste di istruzioni da riprendere più tardi.
EE lor M E { Back (E1.false , M.num ) ; E.true := Fusion (E1.true, E2.true ); E.false := E2.false } EE land M E { Back (E1.true , M.num ) ; E.true := E2.true ; E.false := Fusion (E1.false, E2.false )} Enot E { E.false := E1.true ; E.true := E1.false } E ( E ) { E.true := E1.true ; E.false := E1.false } Eidoprel id { E.true := CreateList (numero ) ; E.false := CreateList (numero + 1) ; Print ( ‘if ’id1.placeoprel.op id2.place ‘ goto’) ; Print ( ‘goto’ ) } Etrue { E.true := CreateList (numero ) ; Print ( ‘goto’ ) } Efalse { E.true := CreateList (numero ) ; Print ( ‘goto’ ) } M{ M.num := numero }
Nell’esempio a < b or c < d and e < f si ottiene finalmente: E.true vale {100, 104} E.false vale {103, 105}. I collegamenti di queste istruzioni saranno completati più tardi. 100 : if a _les b goto 101 : goto 102 102 : if c _les d goto 104 103 : goto 104 : if e _les f goto 105 : goto Si nota che abbiamo utilizzato la valutazione pigra. Implementare la valutazione normale è un pò più complicato. Ora, la traduzioni dei collegamenti condizionali. Iif E then I | if E then I else I | while E do I | beginLend | A LL;I | I dove L è una lista di istruzioni e A un assegnamento.
Per la traduzione, si utilizzano due liste L.next et I.next che contengono i numeri delle istruzioni che contengono un collegamento verso l’istruzione che segue L o I. Caso del while do : Iwhile M E do M I { Back ( E.true, M2.num ) ; Back (I1.next, M1.num ) ; Print ( ‘goto’ M1.num ) ; I.next := E.false } Caso del if then else : Iif E then M I N else M I { Back ( E.truei, M1.num ) ; Back ( E.false, M2.num ) ; I.next := Fusion (I1.next, N.next, I2.next ) } Il marchio N serve a passare sopra il secondo I. N { N.next := CreateList (numero ) ; Print ( ‘goto’ ) }
L’assegnamento inizializza I.next e produce il codice adeguato: Iid:=E { I.next := CreateList () ; Print ( id.place ‘<-’ E.place )} Per la riduzione in una lista : LL ; M I {Back (L1.next, M.num ); L.next := I.next } Si ottiene la grammatica seguente:
Iif E then M I { Back ( E.true, M.num ) ; I.next := Fusion (E.false, I1.next ) } Iif E then M I N else M I { Back ( E.true, M1.num ) ; Back ( E.false, M2.num ) ; I.next := Fusion (I1.next, N.next, I2.next ) } Iwhile M E do M I { Back ( E.true, M2.num ) ; Back (I1.next, M1.num ) ; Prod ( ‘goto’ M1.num ) ; I.next := E.false } M{ M.num := numero } N { N.next := CreateList (numero ) ; Prod ( ‘goto’ ) } Ibegin L end { I.next := L.next } Iid:=E { I.next := CreateList () ; Print ( id.place ‘<-’ E.place )} LL ; M I {Back (L1.next, M.num ); L.next := I.next }
Così, il frammento: if a < b lor c < d land e < f then x := 1 else begin x := 0 ; u := 1 end ; while a < b do x := x + 1 ; produce : 100 : if a _les b goto 106 101 : goto 102 102 : if c _les d goto 104 103 : goto 108 104 : if e _les f goto 106 105 : goto 108 106 : x <- 1 107 : goto 110 108 : x <- 0 109 : u <- 1 110 : if a < b goto 112 111 : goto 112 : t1 := x _plu 1 113 : x <- t1 114 : goto 110 Il risultato è la variabile L e L.next vale {111}.