EL BUEN VIVIR EN LA CONSTITUCIÓN:
2.3.4. Pictogramas como recurso comunicativo
The automatice inference of the annotated types is performed in five stages:
a) In the first stage of the analysis process, a program is parsed and transformed into ghost-let-normal form, as described in the previous section. This interme- diate form is branched out of the compilation process, after possible optimisa- tions have been performed by the compiler. The analysis must be informed of any optimisations, since these might have an affect on whether a cost bound can be guaranteed or not.
b) The second stage then performs the analysis itself on all top-level functions. This is done for each block of mutual recursive functions within the call-graph at once, first starting the with block of functions that do not depend on any other functions. Analysing each block itself consists of three steps:
i) For each block of mutual recursive functions, the type signature is enriched with fresh resource variables. Each function type is non-parametric at first, i.e. for the generic function type ∀α ∈ψ.A−−−→q . q0 C we have bothα and ψ
being empty initially. Together with the types obtained from analysing all previous blocks, this yields an annotated typing signature for all the functions that might be called within the current block.
ii) Afterwards, in the second step of stage two, the defining body of each function is analysed by constructing the Arthurtype derivation. This is
done by traversing the standard typing derivation once only. All occur- ring types are always given fresh resource variables as needed, which are then restricted by subtyping upon application to functions or construc- tors, or by merging the annotated result types of different conditional branches. This process is syntax-driven, without any choice. Structural rules such as subtyping, weakening and sharing are only used when re- quired by the syntax, based on the free variables of subterms, and at the latest point possible. Suptyping simply generates the necessary inequal- ities, similar to sharing. Both always succeed in generating constraints, although the generated constraints might not be solvable in all cases. Rule
Arthur`Relaxis built-into every other rule, so it is effectively applied
in each step.
iii) The third step of the second stage simply refines the annotated signature, by placing the whole generated constraint into the type of each function within the mutual block, setting α simply as the set of all free variables within that constraint set. Note that resource parametricity thus only applies to calls to functions which are strictly below the current function in the call graph. Recursive calls or calls between mutually recursive functions cannot make use of resource parametricity. The reason is that generation of the constraints for a block of mutual recursive functions depends on itself. The problem is similar topolymorphic recursion[Hen93, KTU93] and is likely to require a similarly complicated technique to allow resource parametric recursion.
Note that if there is a main expression given in the program besides function definitions, then it is analysed at the very end of stage two, just using step b.ii above.
c) For the third stage, an objective function is generated based on a heuristic, which generally prefers small values on the left-hand sides of function arrows
and large values for resource variables occurring on the right-hand side of func- tions. A unique slack variable is also introduced for each inequality, turning it into an equation. The slack variables are also minimised by the objective function, in order to minimise waste.
d) The fourth stage consists of simply feeding the constrains to an LP-solver, such as lp solve [BEN]. In practise, we have found that the generated constraints can always be easily solved as they are by lp solve. In fact, attempts to simplify the constraints both in their number and the number of variables used showed an increase in runtime and a loss of numeric stability.
The efficiency of solving the generated constraint sets is not an issue. The LPs generated for the program examples in Chapter 8, which usually involve less than 5000 constraints over less than 10000 variables, can all be solved within a single second on typical contemporary hardware (2.53GHz Intel Mobile Core 2 Duo, with 6MB cache and 4GB memory). Note that in general, even simple linear programs may require a long time to solve. The reason that the large linear programs generated by our amortised analysis technique are easy to solve lies likely in the observation that they always admit an integer solution, if they are solvable at all, as noted in our work [HJ03].
Actual runtime measurements for inferring the annotated types for a number of realistic program examples are given in [LJ09], and are repeated for convenience in Section 9.1, where we discuss the asymptotic complexity of the inference process.
It is noteworthy that the implementation can also print the generated con- straints into a file, both solved and unsolved. Due to the constraint generation in stage two being syntax driven, each constraint can be prefixed with the pre- cise line and column number of the syntactic construct that triggered its gen- eration. Each constraint is furthermore labelled with three letters that identify the applied type rule. It is also possible to see symbolic sums using the names of the resource constants that led to the constant coefficient of a constraint. This yields a very high transparency to the amortised analysis process.
e) For the final stage, any solution that is found by the LP-solver in the previous stage is then presented to the user in the form of an annotated type and/or a human-readable closed cost formula for the analysed program.