githubEdit

Data Flow Analysis

Static Analysis: Control Flow Graph

  1. Ogni nodo del CFG rappresenta un istruzione.

  2. Variabili ed argomenti di funzioni vengono trattati allo stesso modo, come simboli temporanei.

  3. Un arco pqp \rightarrow q può essere eseguito subito dopo qq. In questo caso pp è il predecessore di qq.

  4. Il punto di ingresso non ha predecessori, è chiamato nodo iniziale.

  5. Il punto di uscita (return) non ha successori, è chiamato nodo finale.

  6. Istruzioni non condizionali hanno al più un successore, mentre le istruzioni condizionali ne hanno due (punti di biforcazione). Un nodo con due o più predecessori è chiamato punto di confluenza.

  7. Il CFG può essere visto come un FSA il cui alfabeto di terminali è l'insieme delle istruzioni II, ed il linguaggio delle stringhe generate da uno specifico CFG è l'insieme dei percorsi dallo stato iniziale allo stato finale.

Def\text{Def}, Use\text{Use} sets

Un assegnamento di una variabile è chiamato definizione, mentre un occorrenza all'interno di un espressione viene chiamata uso.

Ogni nodo pp del CFG ha due insiemi di variabili: def(p)\operatorname{def}(p) e uuse(p)u\operatorname{use}(p). Ad esempio, se il nodo contiene la seguente istruzione:

p:a:=abp : a := a \oplus b

I suoi insiemi di definizioni ed utilizzi saranno:

def(p)={a}use(p)={a,b}\operatorname{def}(p) = \{a\} \\ \operatorname{use}(p) = \{a, b\}

Esempio di CFG con usi e definizioni

Definiamo l'insieme delle definizioni e l'insieme degli utilizzi di una generica variabile aa come: D(a)ID(a) \subseteq I e U(a)IU(a) \subseteq I, ad esempio D(a)={1,4}D(a)=\{1,4\} e U(a)={2,5}U(a)=\{2,5\} nell'esempio sottostante:

Esempio di CFG con insiemi di usi e definizioni

Liveness Analysis: Data Flow Equations

Possibili utilizzi di liveness analysis:

  • Useless Definitions: Eliminazioni di definizioni di variabili che non vengono mai usate

  • Common Subexpressions: Se un espressione è già stata computata, può essere memorizzata in un temporary e riutilizzata in seguito. Simile a liveness analysis, ma non considera le singole variabili ma le combinazioni di essere nelle espressioni.

  • Constant Folding: La computazione di espressioni con operandi constanti può essere effettuata a compile time.

  • Constant Propagation: Simile a constant folding, ma se una temporary è costante le sue occorrenze succesive sono rimpiazzate con tale costante.

Arc Liveness

Prendiamo in considerazione un CFG e un arco pq1p \rightarrow q_{1}. Una variabile è live sull'arco se \exists un percorso pq1qnp \rightarrow q_{1} \rightarrow \ldots \rightarrow q_{n}, con n1n \geq 1, che raggiunge qnq_{n} e tale che:

  1. ause(qn)a \in \operatorname{use} \left(q_{n}\right),

  2. Nessuno dei nodi qjq_{j} sul percorso pq1qjqnp \rightarrow q_{1} \rightarrow \ldots q_{j} \rightarrow \ldots \rightarrow q_{n}, ad eccezione di pp e qnq_{n}, contengono una definizione di aa.

Idea: controllo bottom-up che alcune definizioni siano live in un percorso specifico del CFG. Con liveness si fa riferimento alla persistenza di uno specifico assegnamento di una variabile in una certa sequenza di istruzioni.

Note

  • qnq_{n} può coincidere con pp.

  • Se pp ha due successori, aa è live all'uscita di pp se è live su uno dei due archi.

  • aa è live all'ingresso di pp se è live su tutti i vertici che entrano in pp.

  • Il fatto che aa sia live all'uscita di pp non implica che adef(p)a \in \operatorname{def}(p).

Liveness Property

La liveness property di aa all'uscita di un nodo pp è definita dalla seguente condizione:

Nel linguaggio L(A)L(A) esiste una stringa (un percorso) composta da:

  • Un generico percorso dal nodo iniziale

  • pp

  • Un generico percorso in cui aa non è mai definita: vD(a)=0v \cap D(a) = 0

  • Un nodo qq che utilizza aa: ause(q)a \in \operatorname{use} \left(q\right)

  • Un generico percorso fino al nodo finale

L'insieme di tutte le stringhe che rispettano questa condizione formano un linguaggio regolare. Questa strategia non è molto efficiente, esiste un metodo più pratico.

Data Flow Equations

Sistema di equazioni tali per cui per ogni nodo pp abbiamo:

  1. Un equazione che correla le variabili live all'ingresso e live all'uscita di esso.

  2. Un equazione che correla le variabili live all'uscita di pp con quelle live all'ingresso dei suoi immediate succesors.

Siano livein(p)\operatorname{live}{ }_{i n}(p) and liveout (p)\operatorname{live}{ }_{\text {out }}(p) gli insiemi di variabili live all'ingresso e all'uscita di pp, e suc(p),pred(p)\operatorname{suc}(p), \operatorname{pred}(p) gli immediate successors e immediate predecessors di pp:

Per un nodo finale pp :

 live out (p)=\text { live }_{\text {out }}(p)=\emptyset

Per ogni altro nodo pp :

livein(p)= use (p)( live out (p)\def(p))liveout (p)=qsucc(p) live in (q)\begin{array}{rll} \operatorname{live}_{i n}(p)= & \text { use }(p) \cup\left(\text { live }_{\text {out }}(p) \backslash \operatorname{def}(p)\right) \\ \operatorname{live}_{\text {out }}(p)= & \bigsqcup_{\forall q \in \operatorname{succ}(p)} \text { live }_{\text {in }}(q) \end{array}
  1. Equazione 1: nessuna variabile è live all'uscita del programma

  2. Equazione 2: una variabile è live all'ingresso di pp se è utilizzata in pp o se è live all'uscita di pp e non è definita in esso.

  3. Equazione 3: L'insieme delle variabili live all'uscita di pp è l'unione di tutte le variabili live all'ingresso dei successori di pp.

Fixed Point Solution

Il sistema può essere risolto iterativamente, partendo dall'insieme vuoto all'iterazione iniziale (i=0i = 0):

p:livein(p)=;liveout (p)=\forall p: \operatorname{live}_{i n}(p)=\emptyset ; \operatorname{live}_{\text {out }}(p)=\emptyset

I valori sono ricavati sostituendo le variabili sconosciute con gli insiemi vuoti, ottenendo una nuova iterazione. La soluzione è ricavata quando due iterazioni successive convergono (i.e. sono uguali).

Proprietà delle Data Flow Equations, Dimostrazione di terminazione

  1. Cardinalità limitata: Sia livein(p)\operatorname{live}_{i n}(p) che liveout(p)\operatorname{live}_{out}(p) hanno una cardinalità limitata, il cui limite superiore è la cardinalità dell'insieme delle variabili del programma. È importante fare in modo che questa proprietà sia rispettata quando gli insiemi delle proprietà vengono definiti.

  2. Monotonicità: Non rimuoviamo mai variabili da un'approssimazione precedente, cioè non rimuoviamo mai una variabile dal'insieme corrente. Nello specifico la seconda equazione (livein(p)= use (p)( live out (p)\def(p))\operatorname{live}_{i n}(p)= \text { use }(p) \cup\left(\text { live }_{\text {out }}(p) \backslash \operatorname{def}(p)\right)) Non deve mai rimuovere elementi dall'insieme.

  3. Terminazione: Proprietà strutturale dell'algoritmo, se un iterazione non modifica il risultato ottenuto all'iterazione precedente, l'algoritmo si ferma.

Di conseguenza l'algoritmo può aggiungere al massimo tutte le variabili a tutti i live sets, e poi deve terminare dal momento che la proprietà di monotonicità gli impedisce di far ripartire il ciclo da un'altra soluzione.

Nota: questa dimostrazione è chiesta quasi sempre all'esame.

Esempio

Esempio di Data Flow Equations
Liveness Analysis, iterazioni dell'algoritmo

Time Complexity: Worst Case Analysis

for each nn in(n); out (n); initialization  for each nin(n)in(n); out (n)out(n)in(n)cose(n)(out(n)\def(n))out(n)qsucc(p)in(q)n:in(n)=in(n)out(n)=out(n)\begin{array}{ll}\operatorname{in}(n) \leftarrow \emptyset ; \quad & \text { out }(n) \leftarrow \emptyset ; \quad-\text { initialization } \\ \text { for each } n & \\ & \operatorname{in}^{\prime}(n) \leftarrow \operatorname{in}(n) ; \text { out }(n) \leftarrow \operatorname{out}(n) \\ & \operatorname{in}(n) \leftarrow \operatorname{cose}(n) \cup(\operatorname{out}(n) \backslash \operatorname{def}(n)) \\ & \operatorname{out}(n) \leftarrow \bigsqcup_{\forall q \in \operatorname{succ}(p)} \operatorname{in}(q) \\ \forall n: \operatorname{in}^{\prime}(n)= & \operatorname{in}(n) \wedge \operatorname{out}^{\prime}(n)=\operatorname{out}(n)\end{array}

  1. Ogni \cup sugli insiemi live-in o live-out -> O(N)O(N)

  2. Il ciclo interno calcola un numero costante di unioni per nodo -> O(k)O(k)

  3. Ci sono O(N)O(N) nodi -> il ciclo for ha complessità O(N2)O\left(N^{2}\right)

  4. Ogni iterazione deve aggiungere qualche elemento agli insiemi, ma ogn insieme ha cardinalità con limite superiore NN. La somma delle cardinalità ha limite superiore 2N22N^2 -> il loop può essere eseguito al più O(N2)O(N^2) volte.

Complessità totale del caso pessimo: O(N4)O\left(N^{4}\right).

Rappresentazione in memoria dei in\text{in}, out\text{out} sets

  1. Bit Vectors: array di bits in cui ogni bit rappresenta un elemento. Dato NN, servono Nwordsize\frac{N}{\text{wordsize}} words per rappresentare un insieme. Se NN è relativamente fissato, la complessità per effettuare l'unione diventa costante.

  2. Linked lists: elementi rappresentati da una linked list. In questo caso il costo dell'unione è proporzionale ad NN, e tale operazione è effettuata unendo le due liste senza elementi ripetuti. Non sono quasi mai più veloci dei bit vectors.

Quando i set sono sparsi è preferibile il secondo caso, altrimenti il primo.

Reaching Definitions

Definizioni ambigue e non ambigue

Definizione di una variabile che raggiunge (reaching) un certo punto nel programma. Tipologia di analisi collegata a SSA in quanto il contenuto delle ϕ\phi functions sono le definizioni delle variabili che raggiungono un certo nodo pp.

L'istruzione p:a:=abp : a := a \oplus b definisce aa. Denotiamola come apa_p.

  • Una definizione ambigua è una definizione della variabile aa che potrebbe non assegnare un valore ad essa.

  • Insieme delle definizioni di aa nel programma: D(a)D(a). Una definizione certa (o non ambigua) di aa in un istruzione qq, chiamiamola aqa_q, è una definizione che raggiunge l'ingresso di un istruzione pp (che può coincidere con qq) se esiste nel CFG un percorso da qq a pp che non attraversa un nodo in cui aa viene ridefinita, e di conseguenza pp userà il valore aqa_q.

gen\text{gen}, kill\text{kill} sets

Dato uno generico nodo pp definiamo:

  • gen(p)={ap}\operatorname{gen}(p) = \{ a_p \}, contiene cioè le variabili definite da pp.

  • kill(p)=D(a)\{ap}\operatorname{kill}(p) = \operatorname{D}(a) \backslash \{ a_p \}. cioè l'insieme di tutte le variabili ridefinite da pp, cioè variabili che sono sia definite da pp, sia definite da almeno un altro nodo, chiamamolo qq, tale per cui pqp \neq q.

Più formalmente, dato II, l'insieme delle istruzioni del programma, consideriamo un istruzione pp che definisce una variabile (i.e. produce apa_p) e sovrascrive (kills) ogni altra definizione di aqa_q, qpq \neq p:

gen(p)={ap}{kill(p)={aqqIqpadef(q)adef(p)}, if def(p)kill(p)=, if def(p)=\begin{gathered} \operatorname{gen}(p)=\left\{a_{p}\right\} \\ \left\{\begin{array}{rlr} \operatorname{kill}(p)= & \left\{a_{q} \mid q \in I \wedge q \neq p\right. & \\ & \wedge a \in \operatorname{def}(q) \wedge a \in \operatorname{def}(p)\}, & \text { if } \operatorname{def}(p) \neq \emptyset \\ \operatorname{kill}(p) = & \emptyset, & \text { if } \operatorname{def}(p)=\emptyset \end{array}\right. \end{gathered}

Computazione delle in\text{in}, out\text{out} equations

Gli insiemi delle definizioni che raggiungono l'ingresso e l'uscita di pp sono chiamati rispettivamente in\operatorname{in} e out\operatorname{out}. Per un generico nodo pp definiamo in(p)\text{in}(p) come l'unione degli insiemi out(q)\text{out}(q) q\forall q predecessore di pp, e out(p)\text{out}(p) come l'unione tra l'insieme delle variabili definite da pp e l'insieme risultante dalla differenza tra in(p)\text{in}(p) e kill(p)\text{kill}(p).

  • Per il nodo iniziale 1:1:

in(1)=\operatorname{in}(1)=\emptyset
  • Per ogni altro nodo pp :

out(p)=gen(p)(in(p)\kill(p))in(p)=qpred(p)out(q)\begin{aligned} \operatorname{out}(p) &=\operatorname{gen}(p) \cup(\operatorname{in}(p) \backslash \operatorname{kill}(p)) \\ \operatorname{in}(p) &=\bigsqcup_{\forall q \in \operatorname{pred}(p)} \operatorname{out}(q) \end{aligned}

Dove pred(p)\operatorname{pred}(p) definisce i predecessori immediati di pp nel CFG. Inoltre:

  • Da in(1)=\operatorname{in}(1)=\emptyset notiamo che gli argomenti del programma non sono tenuti in cosiderazione.

  • Per quanto riguarda la liveness, le equazioni possono essere risolte iterativamente terminando all'iterazione in cui tutti gli insiemi delle incognite sono vuoti, con l'eccezione di in(1)\operatorname{in}(1), che è sempre vuoto.

Last updated