1
|
(* We try to solve all algebraic loops (AL) from prog:
|
2
|
each node is mapped to its own cycles
|
3
|
each cycle is tentatively solved by inlining one of its component
|
4
|
|
5
|
When done, a report is generated
|
6
|
|
7
|
- for each initial AL, the cycle is presented
|
8
|
- either it is solvable and we provide the set of inlines that solves it
|
9
|
- or it is not and we write the AL as unsolvable by inlining
|
10
|
|
11
|
If the option solve_al is activated, the resulting partially inlined prog is
|
12
|
propagated fur future processing
|
13
|
Otherwise the program stops
|
14
|
*)
|
15
|
open LustreSpec
|
16
|
open Corelang
|
17
|
open Utils
|
18
|
open Format
|
19
|
|
20
|
(* An single algebraic loop is defined (partition, node calls, inlined, status)
|
21
|
ie the list of variables in the loop, ident list
|
22
|
the possible functions identifier to inline, and whether they have been inlined yet (ident * tag * bool) list
|
23
|
and a status whether the inlining is enough bool
|
24
|
*)
|
25
|
type algebraic_loop = (ident list * (ident * expr * bool) list * bool)
|
26
|
|
27
|
exception Error of (node_desc * (algebraic_loop list)) list
|
28
|
|
29
|
(* Module that extract from the DataCycle the set of node that could be inlined
|
30
|
to solve the problem. *)
|
31
|
module CycleResolution =
|
32
|
struct
|
33
|
|
34
|
(* We iter over calls in node defs. If the call defined on of the variable in
|
35
|
the cycle, we store it for future possible inline. *)
|
36
|
let resolve node partition =
|
37
|
let partition = ISet.of_list partition in
|
38
|
(* Preprocessing calls: associate to each of them the eq.lhs associated *)
|
39
|
let eqs = get_node_eqs node in
|
40
|
let calls_expr = Causality.NodeDep.get_calls (fun _ -> true) eqs in
|
41
|
let calls = List.map (
|
42
|
fun expr ->
|
43
|
let eq = List.find (fun eq ->
|
44
|
Corelang.expr_contains_expr expr.expr_tag eq.eq_rhs
|
45
|
) eqs in
|
46
|
let fun_name = match expr.expr_desc with
|
47
|
| Expr_appl(fun_id, _, _) -> fun_id
|
48
|
| _ -> assert false
|
49
|
in
|
50
|
eq, expr, fun_name
|
51
|
) calls_expr in
|
52
|
List.fold_left (
|
53
|
fun accu ((eq, _, _) as call) ->
|
54
|
let shared_vars = ISet.inter (ISet.of_list eq.eq_lhs) partition in
|
55
|
if not (ISet.is_empty shared_vars) then
|
56
|
(* We have a match: keep the eq and the expr to inline *)
|
57
|
call::accu
|
58
|
else
|
59
|
accu
|
60
|
) [] calls
|
61
|
end
|
62
|
|
63
|
|
64
|
(* Format.fprintf fmt "@[<v 2>Possible resolution:@ %a@]" pp_resolution resolution*)
|
65
|
|
66
|
|
67
|
let pp_resolution fmt resolution =
|
68
|
fprintf_list ~sep:"@ " (fun fmt (eq, tag) ->
|
69
|
Format.fprintf fmt "inlining: %a" Printers.pp_node_eq eq
|
70
|
) fmt resolution
|
71
|
|
72
|
let al_is_solved al = true
|
73
|
|
74
|
(********************)
|
75
|
|
76
|
let inline_annotation loc =
|
77
|
Inliner.keyword,
|
78
|
Corelang.mkeexpr loc
|
79
|
(Corelang.mkexpr loc
|
80
|
(Expr_const (Const_tag tag_true) ))
|
81
|
|
82
|
let is_expr_inlined expr =
|
83
|
match expr.expr_annot with
|
84
|
None -> false
|
85
|
| Some anns -> if List.mem_assoc Inliner.keyword anns.annots then
|
86
|
let status = List.assoc Inliner.keyword anns.annots in
|
87
|
match status.eexpr_qfexpr.expr_desc with
|
88
|
| Expr_const (Const_tag tag) when tag = tag_true ->
|
89
|
true
|
90
|
| Expr_const (Const_tag tag) when tag = tag_false ->
|
91
|
false
|
92
|
| _ -> assert false (* expecting true or false value *)
|
93
|
else false
|
94
|
|
95
|
(* Inline the provided expression *)
|
96
|
let inline_expr expr =
|
97
|
let ann = inline_annotation expr.expr_loc in
|
98
|
match expr.expr_annot with
|
99
|
| None -> expr.expr_annot = Some {annots = [ann]; annot_loc = expr.expr_loc}
|
100
|
| Some anns -> expr.expr_annot = Some {anns with annots = ann::anns.annots}
|
101
|
|
102
|
(* Inline the call tagged tag in expr: should not be used anymore, thanks to a direct call to inline_expr *)
|
103
|
let rec inline_call tag expr =
|
104
|
if expr.expr_tag = tag then
|
105
|
inline_expr expr
|
106
|
else
|
107
|
let _in = inline_call tag in
|
108
|
let _ins = List.exists _in
|
109
|
in
|
110
|
match expr.expr_desc with
|
111
|
| Expr_const _
|
112
|
| Expr_ident _ -> false
|
113
|
| Expr_tuple el -> _ins el
|
114
|
| Expr_ite (g,t,e) -> _ins [g;t;e]
|
115
|
| Expr_arrow (e1, e2)
|
116
|
| Expr_fby (e1, e2) -> _ins [e1; e2]
|
117
|
| Expr_array el -> _ins el
|
118
|
| Expr_access (e, _)
|
119
|
| Expr_power (e, _)
|
120
|
| Expr_pre e
|
121
|
| Expr_when (e, _, _) -> _in e
|
122
|
| Expr_merge (_, hl) -> _ins (List.map snd hl)
|
123
|
| Expr_appl (_, arg, Some r) -> _ins [arg; r]
|
124
|
| Expr_appl (_, arg, None) -> _in arg
|
125
|
|
126
|
|
127
|
(**********************)
|
128
|
(* Returns a modified node, if possible, and an algebraic_loop report *)
|
129
|
let rec solving_node nd existing_al partitions =
|
130
|
(* For each partition, we identify the original one *)
|
131
|
let rerun, al = List.fold_left (fun (rerun, al) part ->
|
132
|
let part_vars = ISet.of_list part in
|
133
|
let match_al (vars, calls, status) =
|
134
|
not (ISet.is_empty (ISet.inter (ISet.of_list vars) part_vars)) in
|
135
|
let matched, non_matched = List.partition match_al existing_al in
|
136
|
match matched, non_matched with
|
137
|
| [], _ -> ((* A new conflict partition *)
|
138
|
let calls = CycleResolution.resolve nd part in
|
139
|
(* Filter out already inlined calls *)
|
140
|
let already_inlined, possible_resolution =
|
141
|
List.partition (fun (_, expr, _) -> is_expr_inlined expr) calls in
|
142
|
(* Inlining the first uninlined call *)
|
143
|
match possible_resolution with
|
144
|
| (eq, expr, fun_id)::_ -> ((* One could inline expr *)
|
145
|
(* Forcing the expr to be inlined *)
|
146
|
let _ = inline_expr expr in
|
147
|
true, (* we have to rerun to see if the inlined expression solves the issue *)
|
148
|
(
|
149
|
part,
|
150
|
List.map (fun (eq, expr2, fcn_name) -> fcn_name, expr2, (expr2.expr_tag = expr.expr_tag)) calls,
|
151
|
false (* Status is nok, LA is unsolved yet *)
|
152
|
|
153
|
)::non_matched
|
154
|
)
|
155
|
| [] -> (* No more calls to inline: algebraic loop is not solvable *)
|
156
|
rerun, (* we don't force rerun since the current node is not solvable *)
|
157
|
(
|
158
|
part, (* initial list of troublesome variables *)
|
159
|
List.map (fun (eq, expr, fcn_name) -> fcn_name, expr, false) calls,
|
160
|
false (* Status is nok, LA is unsolved *)
|
161
|
)::non_matched
|
162
|
|
163
|
)
|
164
|
| [vars, calls, status], _ ->
|
165
|
assert false (* Existing partitions were already addressed:
|
166
|
on peut regarder si la partition courante concerne xxx
|
167
|
else (* New cycle ??? *)
|
168
|
|
169
|
|
170
|
|
171
|
|
172
|
Que faut il faire?
|
173
|
|
174
|
dans un premiertemps on peut ramasser les contraintes et s'arreter là.
|
175
|
pas d'appel recursif donc on a toujours des nouvelles partitions
|
176
|
on calcule les apples, on les associe à faux (on a pas inliné encore)
|
177
|
et le statue global est à faux (on a rien resolu)
|
178
|
|
179
|
comment resoud-on ?
|
180
|
on ne doit se focaliser que sur les partitions courantes. C'est elle qui sont en conflit
|
181
|
pour chaque, on pick un call et on l'inlinee
|
182
|
apres on appel stage1/stage2 pour faire le scheudling du node
|
183
|
|
184
|
|
185
|
*)
|
186
|
|
187
|
|
188
|
) (false, existing_al) partitions
|
189
|
in
|
190
|
(* if partition an already identified al ? *)
|
191
|
(* if ISet.of_list partition *)
|
192
|
|
193
|
(* if rerun then *)
|
194
|
(* assert false (\* the missing part, redo stage 1, ... with the updated inline flags *\) *)
|
195
|
(* ; *)
|
196
|
(* TODO xxx il faut ici ajouter le inline sur le 1er appel,
|
197
|
puis reafaire le stage1 et scheduling du noeud *)
|
198
|
Some(nd, al)
|
199
|
|
200
|
(** This function takes a prog and returns (prog', status, alarms)
|
201
|
where prog' is a modified prog with some locally inlined calls
|
202
|
status is true is the final prog' is schedulable, ie no algebraic loop
|
203
|
In case of failure, ie. inlining does not solve the problem; the status is false.
|
204
|
Alarms contain the list of inlining performed or advised for each node.
|
205
|
This could be provided as a feedback to the user.
|
206
|
*)
|
207
|
let clean_al prog =
|
208
|
(* We iterate over each node *)
|
209
|
let prog, al_list =
|
210
|
List.fold_right (
|
211
|
fun top (prog_accu, al_list) ->
|
212
|
match top.top_decl_desc with
|
213
|
| Node nd -> (
|
214
|
let error =
|
215
|
try (* without exception the node is schedulable; nothing to declare *)
|
216
|
let _ = Scheduling.schedule_node nd in
|
217
|
None
|
218
|
with Causality.Error (Causality.DataCycle partitions) -> solving_node nd [] partitions
|
219
|
in
|
220
|
match error with
|
221
|
| None -> top::prog_accu, al_list (* keep it as is *)
|
222
|
| Some (nd, al) ->
|
223
|
(* returning the updated node, possible solved, as well as the
|
224
|
generated alarms *)
|
225
|
{top with top_decl_desc = Node nd}::prog_accu, (nd, al)::al_list
|
226
|
)
|
227
|
| _ -> top::prog_accu, al_list
|
228
|
) prog ([], [])
|
229
|
in
|
230
|
prog, List.for_all al_is_solved al_list, al_list
|
231
|
|
232
|
|
233
|
(* (ident list * (ident * expr* bool) list * bool) *)
|
234
|
let pp_al fmt (partition, calls, status) =
|
235
|
fprintf fmt "@[<v 0>";
|
236
|
fprintf fmt "variables in the alg. loop: @[<hov 0>%a@]@ "
|
237
|
(fprintf_list ~sep:",@ " pp_print_string) partition;
|
238
|
fprintf fmt "@ involved node calls: @[<v 0>%a@]@ "
|
239
|
(fprintf_list ~sep:",@ "
|
240
|
(fun fmt (funid, expr, status) ->
|
241
|
fprintf fmt "%s" funid;
|
242
|
if status && is_expr_inlined expr then fprintf fmt " (inlining it solves the alg. loop)";
|
243
|
)
|
244
|
) calls;
|
245
|
fprintf fmt "@]"
|
246
|
(* TODO ploc:
|
247
|
First analyse the cycle and identify a list of nodes to be inlined, or node instances
|
248
|
Then two behaviors:
|
249
|
- print that list instead of the unreadable cyclic dependency comment
|
250
|
- modify the node by adding the inline information
|
251
|
- recall the subset of stage1 but restricted to a single node:
|
252
|
- inline locally, typing, clocking (may have to reset the tables first), normalization of the node, mpfr injection
|
253
|
- recall stage2
|
254
|
*)
|
255
|
|
256
|
|
257
|
let pp_report fmt report =
|
258
|
fprintf fmt "@.Algebraic loops in nodes@.";
|
259
|
fprintf_list ~sep:"@."
|
260
|
(fun fmt (nd, als) ->
|
261
|
let top = Corelang.node_from_name (nd.node_id) in
|
262
|
Error.pp_error top.top_decl_loc
|
263
|
(fun fmt ->
|
264
|
fprintf fmt "node %s: {@[<v 0>%a@]}"
|
265
|
nd.node_id
|
266
|
(fprintf_list ~sep:"@ " pp_al) als
|
267
|
)
|
268
|
) fmt report;
|
269
|
fprintf fmt "@."
|
270
|
|
271
|
|
272
|
let analyze prog =
|
273
|
let prog, status_ok, report = clean_al prog in
|
274
|
if !Options.solve_al && status_ok then
|
275
|
Scheduling.schedule_prog prog
|
276
|
else (
|
277
|
(* TODO create a report *)
|
278
|
Format.eprintf "%a" pp_report report;
|
279
|
raise (Corelang.Error (Location.dummy_loc, Error.AlgebraicLoop))
|
280
|
)
|