• No results found

Overall Compilation and Linking

In the previous section, we have discussed the functions CompSurface and CompDepthto compute the guarded actions of the surface and the depth of a statement. These functions have to be called by an overall function Compile that has to provide the additional arguments for the calls to CompSurface and CompDepth. In particular, this function has to provide an associative store that is used in the functions CompSurface and CompDepth.

For dealing with associative stores, we assume the following functions that can be easily implemented via hash tables or binary search trees:

• Function NewAssociativeStore generates a new associative store to which the following calls to NewDef or NewVar refer to.

• Function NewVar() generates a new variable in the current associative

store without already associating a boolean expression with it. Such an association can be made later by function call NewAssoc(x, ϕ).

• Function NewDef(ϕ) checks whether ϕ is already associated with a vari- able in the current associative store. If this is the case, this variable is returned, otherwise, a new variable x is generated by NewVar() and asso- ciated with ϕ by function NewAssoc(x, ϕ). Finally, the generated variable xis returned.

• Function GetExpr(x) returns the expression associated with variable x. Figure5.11 shows the overall compile function. First, it allocates a new as- sociative store by calling function NewAssociativeStore. After this, new vari- ables xstrt and xprmtare allocated for the context conditionsstrt andprmt

of the call to function CompSurface. Using the empty renaming, the call to CompSurfacethen computes the guarded actions of the surface Tsrfc.

After the surface has been compiled, the analogous steps are performed for the compilation of the depth: First, new variables xsusp, xabrt and xstrg are allocated that are used as the compilation context susp, abrt and strg

of the function call to CompDepth. Using the incarnation level 0, the call to CompDepthcomputes then the guarded actions of the depth Tdpth.

Finally, all results are collected in a tuple that is returned. It has to be em- phasized that the ordering of the actions related with the associative store has been chosen with some care: Clearly, it would be possible to first allocate all the variables xstrt, xprmt, xsusp, xabrtand xstrgin a single block before call- ing functions CompSurface and CompDepth. However, there is a good reason for using the code as given in Figure5.11: Typically, the variables generated by the associative store are generated along a total ordering. For example, in the current implementation of the Averest system, the generated variables are obtained by applying a constructor function to an integer index so that the

function Compile(S)

NewAssociativeStore(); // compiling the surface xstrt= NewVar(); xprmt= NewVar();

Tsrfc:= CompSurface({}, xstrt, xprmt, S); // compiling the depth

xsusp= NewVar(); xabrt= NewVar(); xstrg= NewVar(); Tdpth:= CompDepth(0, x

susp, xabrt, xstrg, S); // preparing return value

A := ContentsOfAssociativeStore(); C := (xstrt, xprmt, xsusp, xabrt, xstrg);

return (A, C, Tsrfc

, Tdpth);

end

Fig. 5.11. Overall Compile Function

generated variables are essentially integers. The function NewVar then simply increments the current index, and similarly, function NewDef may simply not modify the store or it will increment the index and add a new association for the generated variable.

Now, if the code is implemented as given in Figure5.11, one can detect

‘breakpoints’in the associative store, where a breakpoint is determined by a variable that is not yet associated with a boolean condition. These breakpoints results only from calls to function NewVar, and inspecting the code reveals that such calls occur only in the following functions:

• in function ModuleCallCompSurface to allocate xinst

• in function ModuleCallCompDepth to allocate xinsdand xterm

• in function Compile before the call to CompSurface to allocate xstrt and xprmt, and

• in function Compile before the call to CompDepth to allocate xsusp, xabrt and xstrg.

Hence, the detection of breakpoints allows one to find the places in the as- sociative store, where a module call or a call to compile the overall surface or depth of the given module are made. Clearly, the first two allocated vari- ables in the associative store are xstrt and xprmt, so that these breakpoints are already the first two variables in the store. Moreover, we know which of the associations were only made for the surface since the variable xsuspis the first one allocated by the compilation of the depth. These observations allows one to implement the expansion of surface module calls more efficiently since it is then sufficient to only integrate the surface part of the corresponding associated store.

It is also important to detect the remaining breakpoints that are due to module calls, since the associative stores of the called modules can then be integrated at the places where the module call has been reached by the com- pile functions. The result will then be exactly the same as if the compiler would have expanded the module at compile time, and therefore the associa- tive store keeps its associations with a read-after-write ordering: All variables that occur in an expression ϕ that is associated with some variable xϕare less

than the variable xϕand thus, their associations occur before the association

of xϕ. This is a very important feature for code generation, since the code

can be evaluated in this ordering. Of course, one can alternatively apply a topological sort using the dependency ordering, but this is not necessary with the tricks mentioned above. We will discuss the use of the breakpoints in the linker below, but first leave out this issue.

In the Averest system, the tuple (A, Tsrfc, Tdpth) generated by the com-

piler is stored in a file that can be read later by the linker. If the linker is called for such a module, it will access the corresponding file to read the tu- ple (A, Tsrfc, Tdpth)generated previously by the compiler. When the linker has

read the tuple, it can determine the final compile context. Recall that the com- piler has onlyallocatedvariables xstrt, xprmt, xsusp, xabrtand xstrg, but did not already associated them with boolean expressions. The compiler cannot make this association since it does not know the context in which the module is called, and indeed, there could be many contexts since there can be many calls to the same module. However, if the linker is called for a module, this context of the outermost module can be determined as follows:

• xstrt= true • xprmt= false • xsusp= false • xabrt= false • xstrg= false

However, simply associating these variables with boolean constants would leave out important optimizations: The linker should propagate these con- stants through the associative store, since many of the expressions stored there will thereby be reduced to boolean constants, so that their association with variables become unnecessary.

During the propagation of boolean constants through the associative store, the linker has to check for a potential breakpoint. If a breakpoint is reached that belongs to a module call, the linker has to recursively link the code of the called module.

We therefore make use of the function Propagate shown in Figure5.15to generate a new store and a substitution %. The substitution % maps variables of the old associative store either to variables of the new store or to one of the boolean constants true and false. Hence, % is a translation from the old to the new associative store.

function Link(nameM)

// read data of compiled module (A, C, Tsrfc, Tdpth) := ReadAIF(nameM); (xstrt, xprmt, xsusp, xabrt, xstrg) :≡ C; (inst, DTsrfc, G trf T, M srfc a , CsrfcT ) :≡ T srfc; (L,insd,term, DTdpth, G abs T , Msrfcb , Mdpth, C dpth T ) :≡ T dpth; Msrfc:= Msrfc a ∪ Msrfcb ;

// determine given compilation context C % := {};

NewAssoc(xstrt, true); % := % ∪ {(xstrt, true)}; NewAssoc(xprmt, false); % := % ∪ {(xprmt, false)}; NewAssoc(xsusp, false); % := % ∪ {(xsusp, false)}; NewAssoc(xabrt, false); % := % ∪ {(xabrt, false)}; NewAssoc(xstrg, false); % := % ∪ {(xstrg, false)}; // remaining breakpoints are due to module calls NewAssociativeStore(); Lsrfc:= ({}, {}, {}); Ldpth:= ({}, {}, {}); (%, Lsrfc, Ldpth) := Propagate(%, Lsrfc, Ldpth, A, Msrfc, Mdpth); (DsrfcL , GLtrf, CLsrfc) :≡ Lsrfc; (DdpthL , Gabs L , C dpth L ) :≡ L dpth;

// substitute guarded actions of this module and add the imported actions Dsrfc:= %(Dsrfc T ) ∪ DLsrfc; Gtrf:= %(Gtrf T) ∪ GLtrf; Csrfc:= %(Csrfc T ) ∪ CLsrfc; Ddpth := %(DdpthT ) ∪ DdpthL ; Gabs := %(GTabs) ∪ GLabs; Cdpth:= %(Cdpth T ) ∪ C dpth L ;

// determine control flow constants NewAssoc(instant,inst); NewAssoc(inside,insd); NewAssoc(terminate,term); // collect results A := ContentsOfAssociativeStore(); Tsrfc:= (Dsrfc, Csrfc); Tdpth:= (Ddpth, Cdpth);

Gabs:= combineABS(Gtrf, Gabs); return (L, A, Tsrfc, Tdpth, Gabs) end

function Propagate(%, Lsrfc

, Ldpth, A, Msrfc, Mdpth)

if(null(A))

// linking complete; the associative store has been elaborated

return (%, Lsrfc, Ldpth); else

(xϕ, ϕ) := head(A);

A := tail(A);

if(isBreakpoint(xϕ, ϕ))

// find corresponding module call

Msrfc:= {(mnameI, mnameM, xinst, mstrt, mprmt, margL) ∈ Msrfc| xinst= xϕ};

Mdpth:= {(mnameI, mnameM, xinsd, xterm, msusp, mabrt, mstrg, margL) ∈ Mdpth| xinsd= xϕ};

// here, either Msrfc= {m} ∧ Mdpth= {}or Msrfc= {} ∧ Mdpth= {m}holds if(null(Mdpth))

// link surface module call (Dsrfc

L , GLtrf, CLsrfc) :≡ Lsrfc;

{(mnameI, mnameM, xinst, mstrt, mprmt, margL)} :≡ Msrfc;

(inst, Dsrfc, Gtrf, Csrfc) := LinkSurfaceModule(mstrt, mprmt, mnameM); NewAssoc(xinst,inst);

% := % ∪ {(xinst,inst)}; Lsrfc:= (Dsrfc∪ Dsrfc

L , Gtrf∪ GLtrf, Csrfc∪ CLsrfc); return Propagate(%, Lsrfc

, Ldpth, A, Msrfc\ Msrfc, Mdpth) else// link depth module call

(DLdpth, GLabs, C dpth L ) :≡ L

dpth;

{(mnameI, mnameM, xinsd, xterm, msusp, mabrt, mstrg, margL)} :≡ Mdpth;

(insd,term, Ddpth, Gabs, Cdpth) := LinkDepthModule(msusp, mabrt, mstrg, mnameM); NewAssoc(xinsd,insd);

NewAssoc(xterm,term);

% := % ∪ {(xinsd,insd), (xterm,term)}; Ldpth:= (Ddpth∪ DLdpth, G abs∪ Gabs L , Cdpth∪ C dpth L ); return Propagate(%, Lsrfc, Ldpth, A, Msrfc, Mdpth\ M dpth) end

else// no module call, thus process association (xϕ, ϕ)

ψ := BoolSimp(%(ϕ)); case ψ of true: % := % ∪ {(xϕ, true)}; false: % := % ∪ {(xϕ, false)}; isVar(ψα): % := % ∪ {(xϕ, ψ)}; else yψ:= NewDef(ψ); % := % ∪ {(xϕ, yψ)}; end return Propagate(%, Lsrfc, Ldpth, A, Msrfc, Mdpth); end end end

Function BoolSimp applies thereby the following boolean simplifications to the topmost boolean operator of the expression it is applied to:

• ¬true = false • ¬false = true • ¬¬ϕ = ϕ • true ∧ ψ = ψ • false ∧ ψ = false • ϕ ∧ true = ϕ • ϕ ∧ false = false • ϕ ∧ ϕ = ϕ • ϕ ∧ ¬ϕ = false • ¬ϕ ∧ ϕ = false • true ∨ ψ = true • false ∨ ψ = ψ • ϕ ∨ true = true • ϕ ∨ false = ϕ • ϕ ∨ ϕ = ϕ • ϕ ∨ ¬ϕ = true • ¬ϕ ∨ ϕ = true

Having computed a new associative store, and the substitution %, the other results of the compiler are substituted.

1. substitute everything with %

2. combine Gtrf and Gabswith CombineABS

3. read in tuple of called modules and integrate their associative stores Having the new store and the substitution %, the linker will next use the substitution to substitute the guarded actions. Clearly, guarded actions whose guards are reduced to false, are left out.

Since the some of the associated variables are no longer used

to be continued Todo!

Finally, reconsider the compilation of sequences {S1; S2}. The definition of strt2 and prmt2 in the depth compilation of a sequence {S1; S2}: We use strt2:=term1∧ ¬strgandprmt2 :=suspabrt. The surface of the sequence {S1; S2} will also compute a surface of S2, and the question to be discussed

here is whether these two surfaces can be both executed within a macro step, and if so, if reincarnation of local variables needs to be handled here.

Indeed, both surfaces can be executed within a macro step: Figure5.16

shows a module that demonstrates this behavior: If the control is currently at location w1 and x holds, we execute b = true but do not rest at w2 (due to the weak abortion). Hence, the loop restarts its body, but this time the abortion statement does not react (since it is not an immediate abortion). Hence, we execute a = true, and since x holds, we do not rest at w1. Consequently, we execute b = true twice and reach then location w2:pause.

// read data of compiled module (A, C, Tsrfc, Tdpth) := ReadAIF(nameM); (xstrt, xprmt, xsusp, xabrt, xstrg) :≡ C; (inst, DTsrfc, G trf T, M srfc a , CsrfcT ) :≡ T srfc; (L,insd,term, DTdpth, G abs T , Msrfcb , Mdpth, C dpth T ) :≡ T dpth; Msrfc:= Msrfc a ∪ Msrfcb ;

// determine given compilation context C % := {(xstrt,strt), (xprmt,prmt)}; NewAssoc(xstrt,strt);

NewAssoc(xprmt,prmt); NewAssoc(xsusp, false); NewAssoc(xabrt, false); NewAssoc(xstrg, false);

// remaining breakpoints are due to module calls Lsrfc:= ({}, {}, {});

Ldpth:= ({}, {}, {});

(%, Lsrfc, Ldpth) := Propagate(%, Lsrfc, Ldpth, A, Msrfc, Mdpth);

(Dsrfc

L , GLtrf, CLsrfc) :≡ Lsrfc;

// substitute guarded actions of this module and add the imported actions Dsrfc := %(DsrfcT ) ∪ DLsrfc; Gtrf := %(GTtrf) ∪ GLtrf; Csrfc:= %(Csrfc T ) ∪ CLsrfc; // return results return (%(inst), Dsrfc, Gtrf, Csrfc) end

// read data of compiled module (A, C, Tsrfc, Tdpth) := ReadAIF(nameM); (xstrt, xprmt, xsusp, xabrt, xstrg) :≡ C; (inst, DTsrfc, G trf T, M srfc a , CsrfcT ) :≡ T srfc; (L,insd,term, DTdpth, G abs T , Msrfcb , Mdpth, C dpth T ) :≡ T dpth; Msrfc:= Msrfc a ∪ Msrfcb ;

// determine given compilation context C % := {(xsusp,susp), (xabrt,abrt), (xstrg,strg)}; NewAssoc(xstrt, false);

NewAssoc(xprmt, false); NewAssoc(xsusp,susp); NewAssoc(xabrt,abrt); NewAssoc(xstrg,strg);

// remaining breakpoints are due to module calls Lsrfc:= ({}, {}, {}); Ldpth:= ({}, {}, {}); (%, Lsrfc, Ldpth) := Propagate(%, Lsrfc, Ldpth, A, Msrfc, Mdpth); (DdpthL , GLabs, C dpth L ) :≡ L dpth;

// substitute guarded actions of this module and add the imported actions Ddpth := %(DdpthT ) ∪ DdpthL ; Gabs := %(GTabs) ∪ G abs L ; Cdpth:= %(Cdpth T ) ∪ C dpth L ; // return results

return (%(insd), %(term), Ddpth, Gabs, Cdpth)

end

Fig. 5.15. Propagating the Compile Context through the Associative Store

module M(event bool ?x,!a,!b) { do weak abort S1  a = true; if(!x) w1:pause; S2  b = true; w2:pause; when(x) while(true) }

Synthesis of Synchronous Systems