INTRODUZIONE 
  
8
programma non alteri alcuna relazione di Input/Output originaria. Questa 
trasformazione è affidata ad uno strumento software, il compilatore parallelizzante, 
che, sulla scorta della analisi del programma in ingresso, rispettivamente l’Analisi del 
Flusso e l’Analisi di Dipendenza Dati, fornisce informazioni utili per l’individuazione 
di quali segmenti di programma sono eseguibili in parallelo (parallelismo implicito). 
In altre parole per l’annotazione del parallelismo ci si affida al compilatore, che 
procederà a scoprire i vincoli strutturali, le dipendenze, presenti in un programma che 
impediscono l’esecuzione in parallelo, ed a classificare questi vincoli ed infine, nei 
casi in cui è possibile, trasformare il programma (restructuring transformations), così 
che essi siamo rimossi. 
Ai fini del corretto utilizzo di questi strumenti è previsto un meccanismo di 
interazione con l’utente (user friendly tool), tutt’oggi i più moderni sistemi di 
compilazione (Parafrase2 [Gir90], SUIF [Hall96], Polaris [Blume96]) sono dotati di 
un vasto insieme di comandi ed opzioni (command line flags) e di sistemi per la 
visualizzazione grafica e manipolazione della rappresentazione intermedia [CFG99]. 
In particolare nel caso di sistemi di compilazione sorgente-sorgente, dove l’output 
prodotto consiste in una versione parallela del codice in cui il parallelismo è annotato 
in un formato che può essere riletto dallo stesso compilatore, l’utente è invogliato alla 
costruzione di un processo ciclico di compilazione, dove ad ogni passo, la 
parallelizzazione del programma è ulteriormente raffinata, attraverso la combinazione 
di conoscenze aggiuntive sulla struttura del programma, fornite dell’utente, e di 
tecniche di compilazione. 
I compilatori parallelizzanti sono utilizzati prevalentemente per la parallelizzazione di 
programmi sequenziali complessi, generalmente con un numero elevato di righe di 
codice, in quanto la loro riscrittura mediante l’ausilio di linguaggi di programmazione 
con costrutti paralleli sarebbe molto difficoltosa. Tuttavia la ristrutturazione 
automatica di programmi sequenziali non fornisce garanzie di miglioramento delle 
prestazioni; la qualità del codice ristrutturato può dipendere sia dalla struttura del 
programma da implementare sia dalla architettura della macchina su cui il programma 
INTRODUZIONE 
  
9
parallelo dovrà operare. Ciò è dovuto al fatto che pur individuando opportunità di 
parallelismo presenti in programmi sequenziali, non è stato implementato alcun 
metodo di trasformazione automatico, tranne in alcuni casi ben definiti, in cui l’ordine 
della sequenza di trasformazioni ristrutturanti da applicare sia dimostrabile garantire 
un reale miglioramento delle prestazioni. 
Il problema di individuare la migliore sequenza di trasformazioni, noto col nome di 
phase ordering [EH00]; è molto complesso, ed una possibile soluzione euristica 
consiste, in fase di sviluppo del compilatore, nel verificare, su parti di codice di cui 
non è nota a priori la sequenza ottimale di trasformazioni, differenti sequenze di 
trasformazioni ristrutturanti, e quindi di misurare quale sequenza produce il codice 
con prestazioni migliori. 
Oggetto di questo lavoro di tesi è lo studio e l’implementazione (prototipazione) di un 
ambiente di programmazione per lo sviluppo di un compilatore fortemente modulare 
in cui le componenti di analisi e di ristrutturazione del codice possono essere 
combinate e ricombinate per ottenere da un lato maggiore flessibilità di utilizzo e 
dall’altro codici con migliori prestazioni computazionali. In particolare l’ambiente 
sviluppato si caratterizza per la semplicità di utilizzo. E’ possibile la codifica ed il test 
di algoritmi per l’analisi e trasformazione del codice, in maniera semplice ed 
efficiente. Questo vincolo progettuale ha fornito criteri per la scelta del linguaggio di 
programmazione con cui implementare un ambiente di sviluppo di compilatori con 
queste caratteristiche. L’attenzione si è infatti rivolta verso i linguaggi di 
manipolazione simbolica ed in particolare verso il più rappresentativo di questi, il 
LISP [McCar60]. Esso possiede caratteristiche uniche che lo rendono lo strumento 
più idoneo per la programmazione e la manipolazione simbolica di strutture dati ed 
algoritmi. L’aspetto più significativo è il fatto che le funzioni, con cui sono descritti 
tutti i processi di calcolo, possono esse rappresentate nelle stesse strutture dati del 
LISP. Questo distingue la programmazione in LISP dagli altri paradigmi di 
programmazione, poiché questi ultimi si fondano sulla tradizionale dicotomia tra dato 
ed algoritmo. Questa funzionalità rende quindi il LISP lo strumento migliore, per la 
INTRODUZIONE 
  
10
scrittura di programmi che manipolano altri programmi come dati, come ad esempio 
gli interpreti ed infine proprio i compilatori [AS85]. 
Il prototipo implementato risulta costituito da tre componenti fondamentali: 
1. Il Front-End per il linguaggio Fortran del compilatore parallelizzante 
Parafrase2. 
2. Un Modulo di Conversione la cui funzione è la trasformazione della 
rappresentazione intermedia costruita dal front-end del compilatore 
parallelizzante Parafrase2 in un formato leggibile e manipolabile dal linguaggio 
LISP. 
3. Una libreria di funzioni LISP per l’analisi del codice (Analisi del Flusso e 
Analisi delle Dipendenze). 
Elemento caratterizzante del prototipo è che l’ambiente di compilazione e di sviluppo 
è costituito per intero dal sistema LISP. E’ infatti possibile compilare codice Fortran 
ed ottenere la relativa rappresentazione intermedia in formato LISP, pur rimanendo 
nel sistema. Questo è stato possibile attraverso l’implementazione di una funzione 
LISP (fortran_front_end) in cui sono integrati i primi due elementi del prototipo, 
rispettivamente il Front-End di Parafrase2 ed il Modulo di Conversione. 
Il lavoro di Tesi è organizzato come segue. 
Nel Capitolo 1, dopo una breve panoramica di carattere generale sui linguaggi di 
programmazione, viene data una definizione formale di cosa sia un programma c, 
scritto in un linguaggio di programmazione, _. In seguito sono introdotte le strutture 
dati utilizzate per la rappresentazione di un programma: l’albero sintattico, la tabella 
simboli ed il grafo di flusso del controllo. Muniti di queste strutture viene fornita la 
definizione di esecuzione sequenziale di un programma e presentato l’algoritmo di 
esecuzione. Infine viene introdotto il paradigma di programmazione simbolico, e 
descritto il linguaggio di programmazione LISP. Nel Capitolo 2 è introdotta l’Analisi 
di Flusso di un Programma. In ordine cronologico, è il primo tipo di analisi del 
codice, effettuato dal compilatore. L’Analisi del Flusso del Programma si suddivide 
nella Analisi del Flusso di Controllo, utile per la ricerca di costrutti iterativi 
INTRODUZIONE 
  
11
all’interno del codice, che sono i maggiori candidati alla parallelizzazione, ed in 
Analisi del Flusso dei Dati, che si occupa della raccolta di informazioni sull’uso delle 
variabili nel programma. Questa tecnica di analisi è anche utilizzata nella fase di 
ottimizzazione del codice per elaboratori di tipo seriale. Il Capitolo 3 ha per oggetto 
l’Analisi di Dipendenza Dati. Questa tecnica di analisi consente di determinare se due 
o più istruzioni differenti referenziano una stessa locazione di memoria (dipendenza 
dati). Se questo non accade allora è possibile assegnare ciascun frammento di codice 
indipendente ad un flusso di controllo diverso, cioè è possibile eseguire questi 
frammenti in contesti di esecuzione concorrenti. Sono esposti i concetti fondamentali, 
in particolare per caratterizzare la dipendenza dati nei costrutti iterativi. È infine 
descritto il Greatest Common Divisor test per la dipendenza dati la cui 
implementazione è descritta nel Capitolo 10. Il Capitolo 4 descrive le principali 
tecniche di ristrutturazione per lo sfruttamento di euristiche di ottimizzazione. Il 
Capitolo 5 descrive le principali tecniche di ristrutturazione dei costrutti ciclici. Nel 
Capitolo 6 vengono esposte le principali problematiche legate alla tecniche di 
programmazione e compilazione di macchine parallele. La definizione di esecuzione 
di un programma viene estesa al caso di un ambiente di calcolo parallelo, è quindi 
fornito un modello minimale per un ambiente di esecuzione parallelo. In questo 
contesto è schematizzato l’algoritmo di esecuzione parallela di un programma c. Il 
capitolo termina con la descrizione delle principali funzionalità dei compilatori. Il 
processo di compilazione è suddiviso in una serie di sottoprocessi chiamati fasi. Dopo 
le fasi di analisi (lessicale, sintattica, semantica) il compilatore genera un 
Rappresentazione Intermedia del codice sorgente, ad uso delle successive fasi del 
processo di compilazione. Nel Capitolo 7 è introdotto il compilatore parallelizzante 
Parafrase2, in particolare sono descritte le strutture dati interne utilizzate dal 
compilatore per la costruzione dell’Albero Sintattico e della Tabella Simboli che 
insieme definiscono la Rappresentazione Intermedia del programma. Il Capitolo 8 ha 
per oggetto la descrizione della procedura di lavoro adottata per la implementazione 
del Modulo di Conversione. Inizialmente il Modulo, scritto in linguaggio C, è stato 
INTRODUZIONE 
  
12
inserito come passo di compilazione nel compilatore Parafrase2 e quindi 
opportunamente verificato. In una seconda fase, si è provveduto a costruire una 
interfaccia tra il front end di Parafrase2, con annesso Modulo di Conversione, con 
l’ambiente di programmazione LISP utilizzato (l’XLISP). Il risultato ottenuto è la 
costruzione di una nuova primitiva LISP: (fortran_front_end), che consente di 
utilizzare il front-end di Parafrase2 con annesso Modulo di Conversione in ambiente 
LISP. Il capitolo termina con esempi di applicazione della funzione che mostrano 
come è strutturata la rappresentazione intermedia ottenuta. Nel Capitolo 9 sono 
riportati gli algoritmi e le funzioni LISP con cui è implementata l’Analisi di Flusso 
del Programma. Per l’Analisi del Flusso di Controllo sono state implementate 
funzioni LISP per la costruzione e la rappresentazione grafica del Grafo di Flusso del 
Controllo (Control Flow Graph) con cui sono rappresentati i potenziali cammini di 
esecuzione del programma. Su questa struttura è stato possibile, in una fase 
successiva, implementare delle funzioni LISP per la individuazione dei costrutti 
iterativi all’interno del CFG (Componenti connesse e Dominatori). Inoltre sono state 
implementate delle funzioni LISP per la costruzione del Grafo delle Dipendenze Dal 
Controllo (Postdominatori). Per l’Analisi del Flusso dei Dati sono state implementate 
funzioni LISP per la soluzione dei principali problemi di analisi data flow definiti nel 
Capitolo 2. Il capitolo termina con la descrizione di alcuni esempi di applicazione 
delle funzioni implementate. 
Infine nel Capitolo 10 è descritta la procedura di lavoro e gli algoritmi utilizzati per 
l’implementazione del test per la dipendenza dati. La codifica degli algoritmi per il 
test ha richiesto, per motivi di efficienza la costruzione in linguaggio C, delle 
procedure che calcolano, rispettivamente, la riduzione a gradino e la risoluzione di 
sistemi di equazioni diofantee. In seguito si è provveduto a costruire una interfaccia 
tra il codice implementato e le strutture dati interne dell’ambiente di programmazione 
XLISP. Si sono così ottenute due nuove primitive al linguaggio LISP : (echelon) 
e (diophantine). E’ quindi descritta la funzione LISP (test-dipendenza) 
che consente di impostare il sistema di equazioni diofanteee per il calcolo della 
INTRODUZIONE 
  
13
dipendenza dati. Sono riportati gli algoritmi utilizzati per il test e descritte le 
principali funzioni LISP implementate. Il capitolo termina con la descrizione di alcuni 
esempi di applicazione del test e della implementazione di una tecnica di 
trasformazione di costrutti ciclici nota come loop interchanging, che fa uso del test di 
dipendenza implementato, anche in questo caso sono riportate le funzioni LISP 
definite e descritto un esempio di applicazione. 
 
 
 
CAPITOLO 1 – MODELLI COMPUTAZIONALI DEI PiU’ COMUNI PARADIGMI DI PROGRAMMAZIONE 
  
14
Capitolo 1 - Modelli Computazionali dei 
più comuni paradigmi di programmazione 
1.1 - Linguaggi di Programmazione 
Un linguaggio di programmazione _ deve essere inteso come un linguaggio, nel 
senso delle teorie linguistiche, la cui caratteristica principale è quella di generare 
azioni su un qualche sistema di calcolo. Da questo punto di vista, un linguaggio di 
programmazione è caratterizzato da: 
1) una sintassi, che ne descrive la struttura linguistica, delimitando le frasi ben 
formate mediante, ad esempio, una grammatica; 
2) una semantica, che correla le frasi ben formate del linguaggio alle azioni del 
sistema di calcolo; ed infine, 
3) una pragmatica, il cui ruolo principale è quello di armonizzare la sintassi e 
semantica del linguaggio di programmazione con le limitazioni della teoria 
della computazione e del sistema di calcolo con l’utilizzo concreto del 
linguaggio. 
Ad un linguaggio _ di programmazione è associata una grammatica G
_
 che consente 
di verificare se un programma c, scritto nel linguaggio _, è costituito da frasi ben 
formate. Questo in generale è effettuato in modo automatico nel senso che, per alcune 
classi di grammatiche, è possibile scrivere un programma, detto parser, che riceve in 
ingresso il programma c scritto nel linguaggio _ e verifica, in un tempo finito, la 
correttezza di c rispetto al linguaggio _. 
L’insieme delle frasi ben formate può essere partizionato in classi sintattiche che, in 
generale disgiunte, catturano le funzionalità di base di un linguaggio di 
CAPITOLO 1 – MODELLI COMPUTAZIONALI DEI PiU’ COMUNI PARADIGMI DI PROGRAMMAZIONE 
  
15
programmazione consentendo di realizzare un ponte verso la semantica dello stesso 
linguaggio. Le classi più utilizzate sono: la classe delle frasi dichiarative, e la classe 
delle frasi imperative. Dove le frasi dichiarative sono utilizzate, dal compilatore e dal 
Run Time Language (RTL) per allocare le strutture dati di un programma, mentre le 
frasi imperative sono utilizzate per catturare e modificare, ad ogni istante, lo stato 
della computazione associata al programma. In generale la struttura di 
rappresentazione dello stato di una generica computazione è: 
 
X , V
i
 ⊥ 
i   0
k
 
dove X è l’ambiente della computazione, e 
 
V
i
⊥ 
i   0
k
 sono le continuazioni associate 
alla frase ben formata di tipo imperativa. In generale k=0, cioè vi è una sola 
continuazione, nel caso di istruzione di controllo saranno presenti più di una 
continuazione. Ad esempio nel caso del costrutto: 
if condizione then istruzione
1
 else istruzione
2
 
saranno presenti due continuazioni, la prima relativa al ramo then e la seconda al 
ramo else. 
Per definire la semantica di un linguaggio di programmazione _ è necessario 
individuare l’insieme b degli oggetti su cui il linguaggio opererà. All’interno di un 
linguaggio di programmazione è resa disponibile la nozione di variabile, nozione 
caratterizzata da proprietà che dipendono dal linguaggio di programmazione in uso. 
Tra le più importanti proprietà vanno sicuramente citate:  
 il nome dell’oggetto, cioè una stringa di caratteri con cui individuare 
univocamente l’oggetto denotato
1
; 
 Un insieme di attributi, che specifica i vincoli che dovrà verificare il valore di 
una variabile. Tra questi oltre al tipo, che specifica l’insieme dei valori che la 
                                                 
1
  Si osservi che tale stringa può essere resa sia pubblica, nel senso che l’utente può riferirsi ad essa, 
sia invisibile, nel senso che solo il sistema di RTL può riferirsi, nel qual caso si dirà che l’oggetto è 
anonimo. 
CAPITOLO 1 – MODELLI COMPUTAZIONALI DEI PiU’ COMUNI PARADIGMI DI PROGRAMMAZIONE 
  
16
variabile può assumere, sono presenti quello relativo alla visibilità della 
variabile, cioè la regione di programma in cui è attivo l’insieme degli attributi, 
e l’estensione della variabile, cioè l’intervallo di tempo durante l’esecuzione 
di un programma, nel quale è attivo l’insieme degli atributi. 
 Il riferimento, che indica l’area di registrazione dove il valore della variabile 
verrà conservato. 
Un tipo di dati è rappresentato dalla coppia costituita da una collezione di oggetti (il 
dominio) ed una collezione di operazioni definito sugli oggetti atte a creare, costruire, 
distruggere, modificare e raccogliere le istanze degli oggetti. 
Si dice che un tipo di dati è scalare se il suo dominio è costituito unicamente da valori 
semplici. Esempi di dati scalari sono gli interi, reali, booleani e cararatteri, cioè tipi 
elementari di dati. Contrapposti ai tipi scalari si possono definire i tipi strutturati, per i 
quali il dominio consiste di elementi che sono essi stessi composti da un insieme di 
tipi. Questo implica che gli elementi di un tipo strutturato sono costruiti a partire da 
elementi che a loro volta possono avere tipi propri. 
Esempi di dati strutturati sono gli array ed i record. Dove un array è un aggregato di 
elementi omogenei di dati identificati dalla loro posizione all’interno dell’aggregato, e 
caratterizzati da un nome, da una lista di dimensioni, da un tipo per i propri elementi e 
da un tipo ed un dominio per i valori dei propri indici. Un record è un aggregato di 
elementi (generalmente) eterogenei identificati da un nome sia per l’intero aggregato 
che per gli elementi. 
Nella semantica dei linguaggi di programmazione il termine ambiente si riferisce alla 
funzione che associa ad ogni nome una locazione di memoria, mentre con il termine 
stato indica la funzione che associa ad una locazione di memoria il valore registrato: 
valorememorianome
statoambiente
  ο    ο  
Il legame tra la sintassi di un linguaggio di programmazione _ e la corrispondente 
semantica è generalmente ottenuta associando ad ogni elemento descrittivo della 
sintassi, più in particolare ad ogni regola della grammatica, una funzione che associa 
CAPITOLO 1 – MODELLI COMPUTAZIONALI DEI PiU’ COMUNI PARADIGMI DI PROGRAMMAZIONE 
  
17
le regole di trattamento degli oggetti  2. Questa fase del processo di definizione di un 
linguaggio di programmazione _ è parzialmente automatizzabile, ed in generale si 
utilizza un meccanismo del tipo pattern-matching, cioè si esprimono le regole della 
grammatica G
_
 mediante patterns che debbono essere controllati sulla 
rappresentazione simbolica di ingresso del programma, nel caso in cui un pattern è 
verificato viene avviata l’esecuzione della corrispondente azione. Dove l’azione 
implementerà il vincolo espresso dalla semantica che dalla pragmatica del linguaggio 
di programmazione in considerazione. Da quanto detto è evidente che alcuni aspetti 
della semantica possono essere direttamente implementati e verificati durante la fase 
di analisi sintattica del programma c espresso in un linguaggio di programmazione 
_. 
Vi sono tuttavia alcuni aspetti della semantica e della pragmatica del linguaggio di 
programmazione _ che debbono essere trattati in una fase separata, indicata con 
definizione del Run Time Language. In essa sono confinati tutti quegli aspetti 
necessari alla corretta esecuzione di un programma c espresso nel linguaggio di 
programmazione _. In altre parole nello RTL vengono affrontati tutti i problemi 
relativi al modello di esecuzione soggiacente un linguaggio di programmazione, quali 
ad esempio il meccanismo di passaggio dei parametri fra l’invocazione di una 
procedura rispetto alla sua definizione, la gestione delle strutture dati a visibilità non 
globale, la gestione delle informazioni di controllo, la gestione della disciplina di 
gestione dello stack, e così via. In definitiva possiamo dire che la definizione di un 
sistema che accetti un programma espresso in un linguaggio di programmazione e che 
lo possa eseguire è costituito dalle seguenti componenti software: 
1) un parser, che riconosce se la rappresentazione in ingresso del programma 
scritto nel linguaggio di programmazione _ è ben formata, cioè priva di errori; 
2) un programma che effettua la conversione del programma c in un altro 
linguaggio più semplice da eseguire su un dato sistema di calcolo; 
CAPITOLO 1 – MODELLI COMPUTAZIONALI DEI PiU’ COMUNI PARADIGMI DI PROGRAMMAZIONE 
  
18
3) un ambiente di esecuzione (RTL) che completi l’ambiente disponibile sul 
sistema di calcolo con l’ambiente di esecuzione sotteso al linguaggio di 
programmazione _. 
È ovvio che affinché queste componenti software possano cooperare tra loro 
nell’esecuzione del programma c è necessario che si scambino delle informazioni, a 
questo scopo è necessario definire opportune rappresentazioni intermedie dei 
programmi (Intermediate Program Representation - IPR). In definitiva si ha che lo 
scenario sottostante la progettazione ed esecuzione di un programma espresso in un 
linguaggio di programmazione _ è quello mostrato in Figura 1-1. 
Ambiente di 
esecuzione 
RTL 
Parser 
 
 
 
 
 
 
 
Figura 1-1 : Scenario sottostante l’esecuzione di un programma 
I due elementi fondamentali che legano le istruzioni tra loro sono: il passaggio del 
controllo, ed il flusso dei dati. Assegnato un linguaggio di programmazione _ 
possiamo adottare la seguente definizione di programma: 
Definizione 1.1 
Un programma, scritto nel linguaggio di programmazione _, è rappresentato dalla 
tripla 
  
S
i
⊥ 
i  1
n
,p
s
, 3
I / O
 dove: 
1) S
i
 ⊥ 
i  1
n
 è l’insieme delle istruzioni in cui ciascuna delle 
 
S
i
  _  ; 
CAPITOLO 1 – MODELLI COMPUTAZIONALI DEI PiU’ COMUNI PARADIGMI DI PROGRAMMAZIONE 
  
19
2) 
  
p
s
 è la relazione d’ordine parziale su S
i
 ⊥ 
i  1
n
 e cattura l’ordine di 
esecuzione del programma, compatibile con la semantica di _, per ogni insieme 
di dati di ingresso di c; 
3)  3
I / O
 è l’insieme delle proprietà di input/output che debbono essere 
verificate in ogni esecuzione di c. 
Allora si dovrà trovare una struttura dati per la rappresentazione del programma c 
tale da consentire, allo stesso tempo, di catturare la rappresentazione di S
i
⊥ 
i  1
n
 e la 
relazione 
  
p
s
. È ovvio che i candidati migliori sono le strutture dati ottenute a partire 
dai grafi. Più in particolare avremo che le seguenti strutture dati sono utilizzate per la 
comunicazione tra le varie componenti dell’ambiente di esecuzione di un programma: 
 l’Albero Sintattico Astratto (Abstract Syntax Tree - AST) è prodotto in uscita 
dal parser e fornisce la struttura ad albero utilizzata durante la fase di 
riconoscimento di frase ben formata rispetto al linguaggio di programmazione 
_. 
 la Tabella dei Simboli (Symbol Table - ST) dove sono registrate tutte le 
informazioni sui nomi degli oggetti che si dovranno utilizzare nell’esecuzione 
del programma. 
Da queste strutture dati è estratta la struttura dati che rende esplicita la relazione 
d’ordine delle possibili esecuzioni di un programma, cioè si estrae il passaggio del 
flusso di controllo tra le istruzioni. Questa struttura dati indicata con il nome di Grafo 
del Flusso di Controllo (Control Flow Graph - CFG), può essere così rappresentata: 
CFG = (N, E, Start) dove N è l’insieme dei nodi in cui si articola il controllo, E 
l’insieme degli archi connettenti due nodi correlati tra loro dal passaggio potenziale 
del flusso di controllo, ed Start è il nodo da cui si avvia il controllo dell’intero 
programma, indicato in generale con il nome di nodo di start. 
Più in particolare abbiamo che per un dato programma c = 
 
S
i
 ⊥ 
i  1
n
,p
s
, 3
I / O
 e 
l’associato CFG, dovremo avere che l’in-degree e/o l’out-degree di almeno un nodo 
CAPITOLO 1 – MODELLI COMPUTAZIONALI DEI PiU’ COMUNI PARADIGMI DI PROGRAMMAZIONE 
  
20
x
i
  N  deve essere differente da 1, altrimenti l’intero programma si può ridurre ad un 
solo nodo. Inoltre, se esiste un nodo con in-degree(x
i
) ≥ 1 allora dovrà esistere almeno 
un nodo con out-degree(x
j
) ≥ 1, in altre parole dovrà esistere almeno un’istruzione di 
diramazione nel programma c. 
Si osservi che, in generale, ogni nodo del CFG, a cui è dato il nome di basic block
2
, è 
annotato con informazioni pertinenti il trattamento che il nodo stesso dovrà subire 
durante l’esecuzione del programma da cui è derivato. In questo contesto avremo che 
per un dato programma c = 
  
S
i
⊥ 
i  1
n
,p
s
, 3
I / O
 e l’associato CFG, ogni cammino 
in CFG x
1
, }, x
n
, con x
1
= i (nodo di avvio - Start), x
n
= Stop (nodo di terminazione - 
Stop) ed x
i
  N  è detto essere un’esecuzione sequenziale X
s
 di c. 
Ad ogni nodo del CFG si associano delle annotazioni indicate con Entry_Block(x
i
), 
Exit_Block(x
i
) e Code_Block(x
i
) che debbono rispettivamente essere valutate 
prima e/o dopo l’esecuzione del nodo stesso, e Code_Block è il codice che deve 
essere seguito per l’esecuzione del nodo x
i
. Le informazioni presenti, rispettivamente, 
in Entry_Block ed Exit_Block sono riassunte nella seguente tabella: 
In generale l’algoritmo di esecuzione di un programma così rappresentato può essere 
schematizzato in: 
                                                 
2
 La definizione di basic block è data nel Capitolo 2 
ENTRY Block EXIT Block 
Operazioni indipendenti dal Basic Block: 
1) Allocazione di variabili private del nodo x
i
 e 
dello stack del programma  
1) Propagazione delle informazioni per le 
clausole condizionali 
2) Scelta del nodo successi da eseguire 
Operazioni dipendenti dal Basic Block 
1) Esecuzione del codice di inizializzazione, 
ove esistente 
1) Aggiornamento dei contesti computazionali; 
2) Sincronizzazione con le richieste al Sistema 
Operativo 
CAPITOLO 1 – MODELLI COMPUTAZIONALI DEI PiU’ COMUNI PARADIGMI DI PROGRAMMAZIONE 
  
21
push (START) ; 
while (~ empty_stack ()) { 
 x
i
 = pop () ; 
 evaluate (Entry_Block (x
i
)) ; 
 execute (Code_Block (x
i
)) ; 
 evaluate (Exit_Block (x
i
)) ; 
 push (branch (x
i
)) ; 
} 
Dalle precedenti definizioni abbiamo che una esecuzione sequenziale X
s
 di un 
programma c codificato in un linguaggio di programmazione _ è dato da uno dei 
possibili cammini che vanno dal nodo Start al nodo Stop sul grafo di controllo, la cui 
scelta concreta dipende dai dati di ingresso del programma c. Dal punto di vista 
dell’implementazione, assegnato un programma c, scritto in un linguaggio di 
programmazione _, esso può essere eseguito in due modalità: 
1) la modalità interpretata, dove l’intero ambiente di esecuzione è realizzato da 
un unico programma, detto interprete del linguaggio _, e 
2) la modalità compilata, dove l’ambiente di esecuzione è suddiviso in più 
componenti indipendenti. 
Più in particolare si avrà che il compilatore, tradurrà il programma c dal linguaggio di 
programmazione _ in un programma equivalente che opportunamente trattato dal 
link-editor ed in combinazione con le librerie che implementano il RTL potrà essere 
eseguito. In generale queste due modalità si differenziano per i tempi di sviluppo ed i 
tempi di esecuzione del programma. È evidente che i tempi di sviluppo di un 
programma nella modalità interpretata sono significativamente più bassi rispetto alla 
modalità compilata. Mentre, per quanto riguarda i tempi di esecuzione di un 
programma la modalità interpretata presenta, in generale, tempi di esecuzione 
maggiori della modalità compilata.