1
|
(********************************************************************)
|
2
|
(* *)
|
3
|
(* The LustreC compiler toolset / The LustreC Development Team *)
|
4
|
(* Copyright 2012 - -- ONERA - CNRS - INPT *)
|
5
|
(* *)
|
6
|
(* LustreC is free software, distributed WITHOUT ANY WARRANTY *)
|
7
|
(* under the terms of the GNU Lesser General Public License *)
|
8
|
(* version 2.1. *)
|
9
|
(* *)
|
10
|
(********************************************************************)
|
11
|
|
12
|
open Utils
|
13
|
open Lustre_types
|
14
|
open Corelang
|
15
|
open Causality
|
16
|
|
17
|
type context = {
|
18
|
mutable evaluated : Disjunction.CISet.t;
|
19
|
dep_graph : IdentDepGraph.t;
|
20
|
disjoint : (ident, Disjunction.CISet.t) Hashtbl.t;
|
21
|
policy : (ident, var_decl) Hashtbl.t;
|
22
|
}
|
23
|
|
24
|
type fanin = (ident, tag) Hashtbl.t
|
25
|
|
26
|
(* computes the in-degree for each local variable of node [n], according to dep
|
27
|
graph [g]. *)
|
28
|
let compute_fanin n g =
|
29
|
let locals =
|
30
|
ISet.diff (ExprDep.node_local_variables n) (ExprDep.node_memory_variables n)
|
31
|
in
|
32
|
let inputs = ExprDep.node_input_variables n in
|
33
|
let fanin = Hashtbl.create 23 in
|
34
|
IdentDepGraph.iter_vertex
|
35
|
(fun v ->
|
36
|
if ISet.mem v locals then
|
37
|
Hashtbl.add fanin v (IdentDepGraph.in_degree g v)
|
38
|
else if ExprDep.is_read_var v && not (ISet.mem v inputs) then
|
39
|
Hashtbl.add fanin (ExprDep.undo_read_var v)
|
40
|
(IdentDepGraph.in_degree g v))
|
41
|
g;
|
42
|
fanin
|
43
|
|
44
|
let pp_fanin fmt fanin =
|
45
|
Format.fprintf fmt "@[<v 0>@[<v 2>{ /* locals fanin: */";
|
46
|
Hashtbl.iter (fun s t -> Format.fprintf fmt "@ %s -> %d" s t) fanin;
|
47
|
Format.fprintf fmt "@]@ }@]"
|
48
|
|
49
|
(* computes the cone of influence of a given [var] wrt a dependency graph [g]. *)
|
50
|
let cone_of_influence g var =
|
51
|
(*Format.printf "DEBUG coi: %s@." var;*)
|
52
|
let frontier = ref (ISet.add var ISet.empty) in
|
53
|
let explored = ref ISet.empty in
|
54
|
let coi = ref ISet.empty in
|
55
|
while not (ISet.is_empty !frontier) do
|
56
|
let head = ISet.min_elt !frontier in
|
57
|
(*Format.printf "DEBUG head: %s@." head;*)
|
58
|
frontier := ISet.remove head !frontier;
|
59
|
explored := ISet.add head !explored;
|
60
|
if ExprDep.is_read_var head then
|
61
|
coi := ISet.add (ExprDep.undo_read_var head) !coi;
|
62
|
List.iter
|
63
|
(fun s ->
|
64
|
if not (ISet.mem s !explored) then frontier := ISet.add s !frontier)
|
65
|
(IdentDepGraph.succ g head)
|
66
|
done;
|
67
|
!coi
|
68
|
|
69
|
let compute_unused_variables n g =
|
70
|
let inputs = ExprDep.node_input_variables n in
|
71
|
let mems = ExprDep.node_memory_variables n in
|
72
|
let outputs = ExprDep.node_output_variables n in
|
73
|
ISet.fold
|
74
|
(fun var unused -> ISet.diff unused (cone_of_influence g var))
|
75
|
(ISet.union outputs mems) (ISet.union inputs mems)
|
76
|
|
77
|
(* computes the set of potentially reusable variables. We don't reuse input
|
78
|
variables, due to possible aliasing *)
|
79
|
let node_reusable_variables node =
|
80
|
let mems = ExprDep.node_memory_variables node in
|
81
|
List.fold_left
|
82
|
(fun acc l ->
|
83
|
if ISet.mem l.var_id mems then acc else Disjunction.CISet.add l acc)
|
84
|
Disjunction.CISet.empty node.node_locals
|
85
|
|
86
|
let kill_instance_variables ctx inst =
|
87
|
IdentDepGraph.remove_vertex ctx.dep_graph inst
|
88
|
|
89
|
let kill_root ctx head =
|
90
|
IdentDepGraph.iter_succ
|
91
|
(IdentDepGraph.remove_edge ctx.dep_graph head.var_id)
|
92
|
ctx.dep_graph head.var_id
|
93
|
|
94
|
(* Recursively removes useless variables, i.e. [ctx.evaluated] variables that
|
95
|
are current roots of the dep graph [ctx.dep_graph] - [evaluated] is the set
|
96
|
of already evaluated variables, wrt the scheduling - does only remove edges,
|
97
|
not variables themselves - yet, instance variables are removed *)
|
98
|
let remove_roots ctx =
|
99
|
let rem = ref true in
|
100
|
let remaining = ref ctx.evaluated in
|
101
|
while !rem do
|
102
|
rem := false;
|
103
|
let all_roots = graph_roots ctx.dep_graph in
|
104
|
let inst_roots, var_roots =
|
105
|
List.partition
|
106
|
(fun v -> ExprDep.is_instance_var v && v <> Causality.world)
|
107
|
all_roots
|
108
|
in
|
109
|
let frontier_roots =
|
110
|
Disjunction.CISet.filter (fun v -> List.mem v.var_id var_roots) !remaining
|
111
|
in
|
112
|
if not (Disjunction.CISet.is_empty frontier_roots && inst_roots = []) then (
|
113
|
rem := true;
|
114
|
List.iter (kill_instance_variables ctx) inst_roots;
|
115
|
Disjunction.CISet.iter (kill_root ctx) frontier_roots;
|
116
|
remaining := Disjunction.CISet.diff !remaining frontier_roots)
|
117
|
done
|
118
|
|
119
|
(* checks whether a variable is aliasable, depending on its (address) type *)
|
120
|
let is_aliasable var =
|
121
|
(not (!Options.mpfr && Types.is_real_type var.var_type))
|
122
|
&& Types.is_address_type var.var_type
|
123
|
|
124
|
(* checks whether a variable [v] is an input of the [var] equation, with an
|
125
|
address type. if so, [var] could not safely reuse/alias [v], should [v] be
|
126
|
dead in the caller node, because [v] may not be dead in the callee node when
|
127
|
[var] is assigned *)
|
128
|
let is_aliasable_input node var =
|
129
|
let eq_var = get_node_eq var node in
|
130
|
let inputs_var =
|
131
|
match NodeDep.get_callee eq_var.eq_rhs with
|
132
|
| None ->
|
133
|
[]
|
134
|
| Some (_, args) ->
|
135
|
List.fold_right
|
136
|
(fun e r -> match e.expr_desc with Expr_ident id -> id :: r | _ -> r)
|
137
|
args []
|
138
|
in
|
139
|
fun v -> is_aliasable v && List.mem v.var_id inputs_var
|
140
|
|
141
|
(* replace variable [v] by [v'] in graph [g]. [v'] is a dead variable *)
|
142
|
let replace_in_dep_graph v v' g =
|
143
|
IdentDepGraph.add_vertex g v';
|
144
|
IdentDepGraph.iter_succ (fun s -> IdentDepGraph.add_edge g v' s) g v;
|
145
|
IdentDepGraph.iter_pred (fun p -> IdentDepGraph.add_edge g p v') g v;
|
146
|
IdentDepGraph.remove_vertex g v
|
147
|
|
148
|
let pp_reuse_policy fmt policy =
|
149
|
Format.(
|
150
|
fprintf fmt "@[<v 2>{ /* reuse policy */%t@] }" (fun fmt ->
|
151
|
Hashtbl.iter (fun s t -> fprintf fmt "@,%s -> %s" s t.var_id) policy))
|
152
|
|
153
|
let pp_context fmt ctx =
|
154
|
Format.fprintf fmt
|
155
|
"@[<v 2>{ /*BEGIN context */@,\
|
156
|
eval = %a;@,\
|
157
|
graph = %a;@,\
|
158
|
disjoint = %a;@,\
|
159
|
policy = %a;@,\
|
160
|
/* END context */ }@]" Disjunction.pp_ciset ctx.evaluated pp_dep_graph
|
161
|
ctx.dep_graph Disjunction.pp_disjoint_map ctx.disjoint pp_reuse_policy
|
162
|
ctx.policy
|
163
|
|
164
|
(* computes the reusable dependencies of variable [var] in graph [g], once [var]
|
165
|
has been evaluated - [locals] is the set of potentially reusable variables -
|
166
|
[evaluated] is the set of evaluated variables - [quasi] is the set of
|
167
|
quasi-reusable variables - [reusable] is the set of dead/reusable
|
168
|
dependencies of [var] in graph [g] - [policy] is the reuse map (which domain
|
169
|
is [evaluated]) *)
|
170
|
let compute_dependencies heads ctx =
|
171
|
(*Log.report ~level:6 (fun fmt -> Format.fprintf fmt
|
172
|
"compute_reusable_dependencies %a %a %a@." Disjunction.pp_ciset locals
|
173
|
Printers.pp_var_name var pp_context ctx);*)
|
174
|
List.iter (kill_root ctx) heads;
|
175
|
remove_roots ctx
|
176
|
|
177
|
let compute_evaluated heads ctx =
|
178
|
List.iter
|
179
|
(fun head -> ctx.evaluated <- Disjunction.CISet.add head ctx.evaluated)
|
180
|
heads
|
181
|
|
182
|
(* tests whether a variable [v] may be (re)used instead of [var]. The conditions
|
183
|
are: - [v] has been really used ([v] is its own representative) - same type -
|
184
|
[v] is not an aliasable input of the equation defining [var] - [v] is not one
|
185
|
of the current heads (which contain [var]) - [v] is not currently in use *)
|
186
|
let eligible node ctx heads var v =
|
187
|
Hashtbl.find ctx.policy v.var_id == v
|
188
|
&& Typing.eq_ground
|
189
|
(Types.unclock_type var.var_type)
|
190
|
(Types.unclock_type v.var_type)
|
191
|
&& (not (is_aliasable_input node var.var_id v))
|
192
|
&& (not (List.exists (fun h -> h.var_id = v.var_id) heads))
|
193
|
&& (*let repr_v = Hashtbl.find ctx.policy v.var_id*)
|
194
|
not
|
195
|
(Disjunction.CISet.exists
|
196
|
(fun p -> IdentDepGraph.mem_edge ctx.dep_graph p.var_id v.var_id)
|
197
|
ctx.evaluated)
|
198
|
|
199
|
let compute_reuse node ctx heads var =
|
200
|
let disjoint = Hashtbl.find ctx.disjoint var.var_id in
|
201
|
let locally_reusable v =
|
202
|
IdentDepGraph.fold_pred
|
203
|
(fun p r ->
|
204
|
r && Disjunction.CISet.exists (fun d -> p = d.var_id) disjoint)
|
205
|
ctx.dep_graph v.var_id true
|
206
|
in
|
207
|
let eligibles =
|
208
|
if ISet.mem var.var_id (ExprDep.node_memory_variables node) then
|
209
|
Disjunction.CISet.empty
|
210
|
else Disjunction.CISet.filter (eligible node ctx heads var) ctx.evaluated
|
211
|
in
|
212
|
let quasi_dead, live =
|
213
|
Disjunction.CISet.partition locally_reusable eligibles
|
214
|
in
|
215
|
let disjoint_live = Disjunction.CISet.inter disjoint live in
|
216
|
let dead =
|
217
|
Disjunction.CISet.filter
|
218
|
(fun v -> is_graph_root v.var_id ctx.dep_graph)
|
219
|
quasi_dead
|
220
|
in
|
221
|
Log.report ~level:7 (fun fmt ->
|
222
|
Format.fprintf fmt
|
223
|
"@[<v>eligibles : %a@,\
|
224
|
live : %a@,\
|
225
|
disjoint live: %a@,\
|
226
|
dead : %a@,\
|
227
|
@]"
|
228
|
Disjunction.pp_ciset eligibles Disjunction.pp_ciset live
|
229
|
Disjunction.pp_ciset disjoint_live Disjunction.pp_ciset dead);
|
230
|
(try
|
231
|
let reuse =
|
232
|
match Disjunction.CISet.max_elt_opt disjoint_live with
|
233
|
| Some reuse ->
|
234
|
reuse
|
235
|
| None ->
|
236
|
Disjunction.CISet.choose dead
|
237
|
in
|
238
|
IdentDepGraph.add_edge ctx.dep_graph var.var_id reuse.var_id;
|
239
|
Hashtbl.add ctx.policy var.var_id reuse
|
240
|
with Not_found -> Hashtbl.add ctx.policy var.var_id var);
|
241
|
ctx.evaluated <- Disjunction.CISet.add var ctx.evaluated
|
242
|
|
243
|
let compute_reuse_policy node schedule disjoint g =
|
244
|
let ctx =
|
245
|
{
|
246
|
evaluated = Disjunction.CISet.empty;
|
247
|
dep_graph = g;
|
248
|
disjoint;
|
249
|
policy = Hashtbl.create 23;
|
250
|
}
|
251
|
in
|
252
|
List.iter
|
253
|
(fun heads ->
|
254
|
let heads = List.map (fun v -> get_node_var v node) heads in
|
255
|
Log.report ~level:6 (fun fmt ->
|
256
|
Format.(
|
257
|
fprintf fmt
|
258
|
"@[<v>@[<v 2>new context:@,\
|
259
|
%a@]@,\
|
260
|
NEW HEADS:%a@,\
|
261
|
COMPUTE_DEPENDENCIES@,\
|
262
|
@]"
|
263
|
pp_context ctx
|
264
|
(pp_print_list ~pp_open_box:pp_open_hbox ~pp_sep:pp_print_space
|
265
|
(fun fmt head ->
|
266
|
fprintf fmt "%s (%a)" head.var_id Printers.pp_node_eq
|
267
|
(get_node_eq head.var_id node)))
|
268
|
heads));
|
269
|
compute_dependencies heads ctx;
|
270
|
Log.report ~level:6 (fun fmt ->
|
271
|
Format.fprintf fmt "@[<v>@[<v 2>new context:@,%a@]@,COMPUTE_REUSE@,@]"
|
272
|
pp_context ctx);
|
273
|
List.iter (compute_reuse node ctx heads) heads;
|
274
|
(*compute_evaluated heads ctx;*)
|
275
|
Log.report ~level:6 (fun fmt ->
|
276
|
Format.(
|
277
|
fprintf fmt "@[<v>%a@,@]"
|
278
|
(pp_print_list ~pp_open_box:pp_open_vbox0 (fun fmt head ->
|
279
|
fprintf fmt "reuse %s instead of %s"
|
280
|
(Hashtbl.find ctx.policy head.var_id).var_id head.var_id))
|
281
|
heads)))
|
282
|
schedule;
|
283
|
IdentDepGraph.clear ctx.dep_graph;
|
284
|
ctx.policy
|
285
|
|
286
|
(* Reuse policy: - could reuse variables with the same type exactly only
|
287
|
(simple). - reusing variables with different types would involve: - either
|
288
|
dirty castings - or complex inclusion expression (for instance: array <->
|
289
|
array cell, struct <-> struct field) to be able to reuse only some parts of
|
290
|
structured data. ... it seems too complex and potentially unsafe - for node
|
291
|
instance calls: output variables could NOT reuse aliasable input variables,
|
292
|
even if inputs become dead, because the correctness would depend on the
|
293
|
scheduling of the callee (so, the compiling strategy could NOT be modular
|
294
|
anymore). - once a policy is set, we need to: - replace each variable by its
|
295
|
reuse alias. - simplify resulting equations, as we may now have: x = x; --> ;
|
296
|
for scalar vars or: x = &{ f1 = x->f1; f2 = t; } --> x->f2 = t; for struct
|
297
|
vars *)
|
298
|
|
299
|
(* the reuse policy seeks to use less local variables by replacing local
|
300
|
variables, applying the rules in the following order: 1) use another clock
|
301
|
disjoint still live variable, with the greatest possible disjoint clock 2)
|
302
|
reuse a dead variable For the sake of safety, we replace variables by others:
|
303
|
- with the same type - not aliasable (i.e. address type) *)
|
304
|
|
305
|
(* Local Variables: *)
|
306
|
(* compile-command:"make -C .." *)
|
307
|
(* End: *)
|