Cicli in CFG (Control Flow Graph)

 

Definiamo cosa costituisce un ciclo in un CFG [1]. Dobbiamo usare la nozione di nodo dominante un altro per definire i cicli naturali e l’importante classe di CFG riducibili.

 

Nodi dominanti

 

Diremo che il nodo d di un CFG domina n, scritto d dom n, se ogni percorso dal nodo iniziale del CFG fino a n passa attraverso d. Quindi, ogni nodo domina se stesso, e il punto d’ingresso di un ciclo domina tutti i nodi nel ciclo.

 

Esempio 1. Consideriamo il CFG nella Fig.1 con nodo iniziale 1. Il nodo iniziale domina ogni nodo. Il nodo 2 domina solo se stesso, perché il controllo può raggiungere ogni altro controllo solo attraverso un percorso che inizia per .

Il nodo 3 domina tutto tranne 1 e 2. Il nodo 4 domina tutto tranne 1, 2 e 3, mentre tutti i percorsi che partono da 1 devono iniziare oppure . I nodi 5 e 6 dominano solo se stessi, perché il flusso di controllo può saltare uno dei due passando attraverso l’altro. Infine, 7 domina 7, 8, 9, 10; 8 domina 8, 9, 10; 9 e 10 dominano solo se stessi.

 

Un modo utile di presentare le informazioni sui dominatori è di usare un albero, chiamato albero dei dominatori, in cui il nodo iniziale è la radice, e ogni nodo d domina solo i suoi discendenti nell’albero. Nell’esempio 1 è mostrato l’albero di dominazione per il CFG in figura.

L’esistenza di alberi di dominazione è una conseguenza di una proprietà dei dominatori; ogni nodo n ha un unico dominatore immediato m che è l’ultimo dominatore di n su qualunque percorso dal nodo iniziale fino a n. In termini della relazione dom, il dominatore immediato m ha la proprietà che se  e d dom n, allora d dom m.

 

 

 

 

 


 

Cicli naturali

 

Un’importante applicazione delle informazioni sui dominatori sia ha nella determinazione dei cicli in un CFG usato per il perfezionamento. Ci sono due proprietà essenziali di questi cicli:

1 - Un ciclo deve avere un unico punto d’accesso, chiamato “header”. Questo punto d’accesso domina tutti i nodi nel ciclo, oppure potrebbe non essere il solo punto d’ingresso nel ciclo.

2 – Deve esserci almeno un modo per iterare il ciclo (almeno un percorso all’indietro verso l’header).

Un buon modo per trovare tutti i cicli in un CFG è di cercare gli archi nel CFG le cui teste dominano le loro code. (Se è un arco, b è la testa e a è la coda) .

Chiameremo questi archi “archi all’indietro”.

 

Esempio 2 .Nella figura dell’esempio 1 c’è un arco , e 4 dom 7. Analogamente, è un arco, e 7 dom 10. Gli altri archi con questa proprietà sono , , e . Notiamo che questi sono esattamente gli archi che formano i cicli nel CFG.

 

Dato un arco all’indietro  , definiamo il ciclo naturale dell’arco come il nodo b più l’insieme dei nodi che possono raggiungere n senza passare per b.

 

Esempio 3. Il ciclo naturale dell’arco consiste dei nodi 7, 8 e 10, poiché 8 e 10 sono tutti quei nodi che possono raggiungere 10 senza passare per 7. Il ciclo naturale dell’arco consiste dell’intero insieme dei nodi (ma anche dai nodi che formano il percorso ).

 


 

 

CFG riducibili

 

I grafi di flusso che si presentano in pratica frequentemente rientrano nella classe dei CFG riducibili definiti di seguito.

L’esclusivo uso di costrutti strutturati per il controllo del flusso come if-then-else, while-do, continue, e break producono programmi i cui grafi di flusso sono sempre riducibili. Perfino i programmi scritti usando il costrutto goto sono di solito riducibili.

Sono state proposte una gran varietà di definizioni di “CFG riducibili”. Quella che adottiamo qui mette in evidenza una delle più importanti proprietà dei CFG riducibili; in altre parole, non ci sono salti verso un punto interno del ciclo provenienti dall’esterno; l’unico modo per entrare in un ciclo è attraverso il suo header.

Un CFG G è riducibile se e solo se possiamo partizionare gli archi in due gruppi disgiunti, spesso chiamati archi in avanti e archi all’indietro, aventi le seguenti proprietà:

 1.  Gli archi in avanti formano un grafo aciclico in cui ogni nodo può essere raggiunto dal nodo

       iniziale di G.

 2.  Gli archi all’indietro consistono solo di archi le cui teste dominano le loro code.

 

Esempio 4. Il CFG dell’esempio 1 è riducibile. In generale, se conosciamo la relazione dom per un CFG, possiamo trovare e rimuovere tutti gli archi all’indietro. Gli archi rimanenti devono essere gli archi in avanti se il grafo è riducibile,  e per controllare se un CFG è riducibile è sufficiente controllare che gli archi in avanti formino un grafo aciclico. Nel caso del nostro esempio, è facile verificare che se rimuoviamo i cinque archi all’indietro , , , , e , le cui teste dominano le loro code, il grafo rimanente è aciclico.

Esempio 5. Consideriamo il CFG nella figura, il cui nodo iniziale è 1. Questo CFG non ha archi all’indietro, perché nessuna testa di un arco domina la coda di quell’arco. Quindi, potrebbe essere riducibile solo se l’intero grafo fosse aciclico. Ma siccome non lo è, il CFG non è riducibile. Intuitivamente, la ragione per cui questo CFG non è riducibile è che nel ciclo  si può entrare in due punti distinti, al nodo 2 e al nodo 3.

La proprietà chiave dei CFG riducibili per l’analisi dei cicli è che in tali grafi di flusso, ogni insieme di nodi che vogliamo considerare come ciclo deve contenere un arco all’indietro. Infatti, dobbiamo esaminare solo i cicli naturali degli archi all’indietro in modo da trovare tutti i cicli in un programma il cui grafo di flusso è riducibile.

Al contrario, il grafo di flusso del nostro esempio sembra avere un ciclo consistente dai nodi 2 e 3, ma non c’è un arco all’indietro per cui questo sia un ciclo naturale. Infatti, il ciclo ha due header, 2 e 3, che rende inutilizzabili molte tecniche di ottimizzazione utilizzate dai compilatori.

Fortunatamente, i CFG non riducibili compaiono molto raramente nei programmi tanto da rendere di secondaria importanza lo studio di cicli con più di un header.

Ci sono linguaggi, come Bliss and Modula. 2, che permettono solo programmi con CFG riducibili, e molti altri linguaggi che produrranno CFG riducibili se usiamo il goto.