Remove stale comments about the transformation environment parameter.
[libfirm] / ir / be / belistsched.c
1 /*
2  * Copyright (C) 1995-2007 University of Karlsruhe.  All right reserved.
3  *
4  * This file is part of libFirm.
5  *
6  * This file may be distributed and/or modified under the terms of the
7  * GNU General Public License version 2 as published by the Free Software
8  * Foundation and appearing in the file LICENSE.GPL included in the
9  * packaging of this file.
10  *
11  * Licensees holding valid libFirm Professional Edition licenses may use
12  * this file in accordance with the libFirm Commercial License.
13  * Agreement provided with the Software.
14  *
15  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE.
18  */
19
20 /**
21  * @file
22  * @brief       Primitive list scheduling with different node selectors.
23  * @author      Sebastian Hack
24  * @date        20.10.2004
25  * @version     $Id$
26  */
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include <stdio.h>
32 #include <stdarg.h>
33 #include <string.h>
34 #include <limits.h>
35
36 #include "benode_t.h"
37 #include "be_t.h"
38
39 #include "obst.h"
40 #include "list.h"
41 #include "iterator.h"
42
43 #include "iredges_t.h"
44 #include "irgwalk.h"
45 #include "irnode_t.h"
46 #include "irmode_t.h"
47 #include "irdump.h"
48 #include "irprintf_t.h"
49 #include "array.h"
50 #include "debug.h"
51 #include "irtools.h"
52
53 #include "bemodule.h"
54 #include "besched_t.h"
55 #include "beutil.h"
56 #include "belive_t.h"
57 #include "belistsched.h"
58 #include "beschedmris.h"
59 #include "beschedrss.h"
60 #include "bearch_t.h"
61 #include "bestat.h"
62 #include "beirg_t.h"
63
64 #include <libcore/lc_opts.h>
65 #include <libcore/lc_opts_enum.h>
66
67 DEBUG_ONLY(static firm_dbg_module_t *dbg = NULL);
68
69 #define BE_SCHED_NODE(irn) (be_is_Keep(irn) || be_is_CopyKeep(irn) || be_is_RegParams(irn))
70
71 enum {
72         BE_SCHED_SELECT_TRIVIAL,
73         BE_SCHED_SELECT_REGPRESS,
74         BE_SCHED_SELECT_MUCHNIK,
75         BE_SCHED_SELECT_HEUR,
76         BE_SCHED_SELECT_HMUCHNIK,
77         BE_SCHED_SELECT_RANDOM,
78         BE_SCHED_SELECT_NORMAL,
79 };
80
81 enum {
82         BE_SCHED_PREP_NONE = 0,
83         BE_SCHED_PREP_MRIS = 2,
84         BE_SCHED_PREP_RSS  = 3
85 };
86
87 typedef struct _list_sched_options_t {
88         int select;  /**< the node selector */
89         int prep;    /**< schedule preparation */
90 } list_sched_options_t;
91
92 static list_sched_options_t list_sched_options = {
93         BE_SCHED_SELECT_HEUR,     /* mueller heuristic selector */
94         BE_SCHED_PREP_NONE,       /* no scheduling preparation */
95 };
96
97 /* schedule selector options. */
98 static const lc_opt_enum_int_items_t sched_select_items[] = {
99         { "trivial",  BE_SCHED_SELECT_TRIVIAL  },
100         { "random",   BE_SCHED_SELECT_RANDOM   },
101         { "regpress", BE_SCHED_SELECT_REGPRESS },
102         { "normal",   BE_SCHED_SELECT_NORMAL   },
103         { "muchnik",  BE_SCHED_SELECT_MUCHNIK  },
104         { "heur",     BE_SCHED_SELECT_HEUR     },
105         { "hmuchnik", BE_SCHED_SELECT_HMUCHNIK },
106         { NULL,       0 }
107 };
108
109 /* schedule preparation options. */
110 static const lc_opt_enum_int_items_t sched_prep_items[] = {
111         { "none", BE_SCHED_PREP_NONE },
112         { "mris", BE_SCHED_PREP_MRIS },
113         { "rss",  BE_SCHED_PREP_RSS  },
114         { NULL,   0 }
115 };
116
117 static lc_opt_enum_int_var_t sched_select_var = {
118         &list_sched_options.select, sched_select_items
119 };
120
121 static lc_opt_enum_int_var_t sched_prep_var = {
122         &list_sched_options.prep, sched_prep_items
123 };
124
125 static const lc_opt_table_entry_t list_sched_option_table[] = {
126         LC_OPT_ENT_ENUM_PTR("prep",   "schedule preparation",   &sched_prep_var),
127         LC_OPT_ENT_ENUM_PTR("select", "node selector",          &sched_select_var),
128         LC_OPT_LAST
129 };
130
131 /**
132  * All scheduling info needed per node.
133  */
134 typedef struct _sched_irn_t {
135         unsigned num_not_sched_user; /**< The number of not yet scheduled users of this node */
136         unsigned already_sched : 1;  /**< Set if this node is already scheduled */
137 } sched_irn_t;
138
139 /**
140  * Scheduling environment for the whole graph.
141  */
142 typedef struct _sched_env_t {
143         sched_irn_t *sched_info;                    /**< scheduling info per node */
144         const list_sched_selector_t *selector;      /**< The node selector. */
145         const arch_env_t *arch_env;                 /**< The architecture environment. */
146         const ir_graph *irg;                        /**< The graph to schedule. */
147         void *selector_env;                         /**< A pointer to give to the selector. */
148 } sched_env_t;
149
150 /**
151  * Environment for a block scheduler.
152  */
153 typedef struct _block_sched_env_t {
154         sched_irn_t *sched_info;                    /**< scheduling info per node, copied from the global scheduler object */
155         ir_nodeset_t cands;                         /**< the set of candidates */
156         ir_node *block;                             /**< the current block */
157         sched_env_t *sched_env;                     /**< the scheduler environment */
158         ir_nodeset_t live;                          /**< simple liveness during scheduling */
159         const list_sched_selector_t *selector;
160         void *selector_block_env;
161 } block_sched_env_t;
162
163 /**
164  * Returns non-zero if a node must be placed in the schedule.
165  */
166 static INLINE int must_appear_in_schedule(const list_sched_selector_t *sel, void *block_env, const ir_node *irn)
167 {
168         int res = -1;
169
170         /* if there are no uses, don't schedule */
171         if (get_irn_n_edges(irn) < 1)
172                 return 0;
173
174         /* else ask the scheduler */
175         if (sel->to_appear_in_schedule)
176                 res = sel->to_appear_in_schedule(block_env, irn);
177
178         return res >= 0 ? res : ((to_appear_in_schedule(irn) || BE_SCHED_NODE(irn)) && ! is_Unknown(irn));
179 }
180
181 /**
182  * Returns non-zero if the node is already scheduled
183  */
184 static INLINE int is_already_scheduled(block_sched_env_t *env, ir_node *n)
185 {
186         int idx = get_irn_idx(n);
187
188         assert(idx < ARR_LEN(env->sched_info));
189         return env->sched_info[idx].already_sched;
190 }
191
192 /**
193  * Mark a node as already scheduled
194  */
195 static INLINE void set_already_scheduled(block_sched_env_t *env, ir_node *n)
196 {
197         int idx = get_irn_idx(n);
198
199         assert(idx < ARR_LEN(env->sched_info));
200         env->sched_info[idx].already_sched = 1;
201 }
202
203 static void add_to_sched(block_sched_env_t *env, ir_node *irn);
204
205 /**
206  * Try to put a node in the ready set.
207  * @param env   The block scheduler environment.
208  * @param pred  The previous scheduled node.
209  * @param irn   The node to make ready.
210  * @return 1, if the node could be made ready, 0 else.
211  */
212 static INLINE int make_ready(block_sched_env_t *env, ir_node *pred, ir_node *irn)
213 {
214         int i, n;
215
216         /* Blocks cannot be scheduled. */
217         if (is_Block(irn) || get_irn_n_edges(irn) == 0)
218                 return 0;
219
220         /*
221          * Check, if the given ir node is in a different block as the
222          * currently scheduled one. If that is so, don't make the node ready.
223          */
224         if (env->block != get_nodes_block(irn))
225                 return 0;
226
227         for (i = 0, n = get_irn_ins_or_deps(irn); i < n; ++i) {
228                 ir_node *op = get_irn_in_or_dep(irn, i);
229
230                 /* if irn is an End we have keep-alives and op might be a block, skip that */
231                 if (is_Block(op)) {
232                         assert(get_irn_op(irn) == op_End);
233                         continue;
234                 }
235
236                 /* If the operand is local to the scheduled block and not yet
237                  * scheduled, this nodes cannot be made ready, so exit. */
238                 if (! is_already_scheduled(env, op) && get_nodes_block(op) == env->block)
239                         return 0;
240         }
241
242         if (! must_appear_in_schedule(env->selector, env, irn)) {
243                 add_to_sched(env, irn);
244                 DB((dbg, LEVEL_3, "\tmaking immediately available: %+F\n", irn));
245         } else {
246                 ir_nodeset_insert(&env->cands, irn);
247
248                 /* Notify selector about the ready node. */
249                 if (env->selector->node_ready)
250                         env->selector->node_ready(env->selector_block_env, irn, pred);
251
252                 DB((dbg, LEVEL_2, "\tmaking ready: %+F\n", irn));
253         }
254
255     return 1;
256 }
257
258 /**
259  * Try, to make all users of a node ready.
260  * In fact, a usage node can only be made ready, if all its operands
261  * have already been scheduled yet. This is checked by make_ready().
262  * @param env The block schedule environment.
263  * @param irn The node, which usages (successors) are to be made ready.
264  */
265 static void make_users_ready(block_sched_env_t *env, ir_node *irn)
266 {
267         const ir_edge_t *edge;
268
269         /* make all data users ready */
270         foreach_out_edge(irn, edge) {
271                 ir_node *user = get_edge_src_irn(edge);
272
273                 if (! is_Phi(user))
274                         make_ready(env, irn, user);
275         }
276
277         /* and the dependent nodes as well */
278         foreach_out_edge_kind(irn, edge, EDGE_KIND_DEP) {
279                 ir_node *user = get_edge_src_irn(edge);
280
281                 if (! is_Phi(user))
282                         make_ready(env, irn, user);
283         }
284 }
285
286 /**
287  * Returns the number of not yet schedules users.
288  */
289 static INLINE int get_irn_not_sched_user(block_sched_env_t *env, ir_node *n) {
290         int idx = get_irn_idx(n);
291
292         assert(idx < ARR_LEN(env->sched_info));
293         return env->sched_info[idx].num_not_sched_user;
294 }
295
296 /**
297  * Sets the number of not yet schedules users.
298  */
299 static INLINE void set_irn_not_sched_user(block_sched_env_t *env, ir_node *n, int num) {
300         int idx = get_irn_idx(n);
301
302         assert(idx < ARR_LEN(env->sched_info));
303         env->sched_info[idx].num_not_sched_user = num;
304 }
305
306 /**
307  * Add @p num to the number of not yet schedules users and returns the result.
308  */
309 static INLINE int add_irn_not_sched_user(block_sched_env_t *env, ir_node *n, int num) {
310         int idx = get_irn_idx(n);
311
312         assert(idx < ARR_LEN(env->sched_info));
313         env->sched_info[idx].num_not_sched_user += num;
314         return env->sched_info[idx].num_not_sched_user;
315 }
316
317 /**
318  * Returns the number of users of a node having mode datab.
319  */
320 static int get_num_successors(ir_node *irn) {
321         int             sum = 0;
322         const ir_edge_t *edge;
323
324         if (get_irn_mode(irn) == mode_T) {
325                 /* for mode_T nodes: count the users of all Projs */
326                 foreach_out_edge(irn, edge) {
327                         ir_node *proj = get_edge_src_irn(edge);
328                         ir_mode *mode = get_irn_mode(proj);
329
330                         if (mode == mode_T) {
331                                 sum += get_num_successors(proj);
332                         } else if (mode_is_datab(mode)) {
333                                 sum += get_irn_n_edges(proj);
334                         }
335                 }
336         }
337         else {
338                 /* do not count keep-alive edges */
339                 foreach_out_edge(irn, edge) {
340                         if (get_irn_opcode(get_edge_src_irn(edge)) != iro_End)
341                                 sum++;
342                 }
343         }
344
345         return sum;
346 }
347
348 /**
349  * Adds irn to @p live, updates all inputs that this user is scheduled
350  * and counts all of its non scheduled users.
351  */
352 static void update_sched_liveness(block_sched_env_t *env, ir_node *irn) {
353         int i;
354
355         /* ignore Projs */
356         if (is_Proj(irn))
357                 return;
358
359         for (i = get_irn_ins_or_deps(irn) - 1; i >= 0; --i) {
360                 ir_node *in = get_irn_in_or_dep(irn, i);
361
362                 /* if in is a proj: update predecessor */
363                 in = skip_Proj(in);
364
365                 /* if in is still in the live set: reduce number of users by one */
366                 if (ir_nodeset_contains(&env->live, in)) {
367                         if (add_irn_not_sched_user(env, in, -1) <= 0)
368                                 ir_nodeset_remove(&env->live, in);
369                 }
370         }
371
372         /*
373                 get_num_successors returns the number of all users. This includes
374                 users in different blocks as well. As the each block is scheduled separately
375                 the liveness info of those users will not be updated and so these
376                 users will keep up the register pressure as it is desired.
377         */
378         i = get_num_successors(irn);
379         if (i > 0) {
380                 set_irn_not_sched_user(env, irn, i);
381                 ir_nodeset_insert(&env->live, irn);
382         }
383 }
384
385 /**
386  * Append an instruction to a schedule.
387  * @param env The block scheduling environment.
388  * @param irn The node to add to the schedule.
389  * @return    The given node.
390  */
391 static void add_to_sched(block_sched_env_t *env, ir_node *irn)
392 {
393     /* If the node consumes/produces data, it is appended to the schedule
394      * list, otherwise, it is not put into the list */
395     if (must_appear_in_schedule(env->selector, env->selector_block_env, irn)) {
396                 update_sched_liveness(env, irn);
397                 sched_add_before(env->block, irn);
398
399                 DBG((dbg, LEVEL_2, "\tadding %+F\n", irn));
400
401                 /* Remove the node from the ready set */
402                 ir_nodeset_remove(&env->cands, irn);
403     }
404
405         /* notify the selector about the finally selected node. */
406         if (env->selector->node_selected)
407                 env->selector->node_selected(env->selector_block_env, irn);
408
409     /* Insert the node in the set of all available scheduled nodes. */
410     set_already_scheduled(env, irn);
411
412         make_users_ready(env, irn);
413 }
414
415 /**
416  * Perform list scheduling on a block.
417  *
418  * Note, that the caller must compute a linked list of nodes in the block
419  * using the link field before calling this function.
420  *
421  * Also the outs must have been computed.
422  *
423  * @param block The block node.
424  * @param env Scheduling environment.
425  */
426 static void list_sched_block(ir_node *block, void *env_ptr)
427 {
428         sched_env_t *env                      = env_ptr;
429         const list_sched_selector_t *selector = env->selector;
430         ir_node *start_node                   = get_irg_start(get_irn_irg(block));
431
432         block_sched_env_t be;
433         const ir_edge_t *edge;
434         ir_node *irn;
435         int j, m;
436
437         /* Initialize the block's list head that will hold the schedule. */
438         sched_init_block(block);
439
440         /* Initialize the block scheduling environment */
441         be.sched_info = env->sched_info;
442         be.block      = block;
443         ir_nodeset_init_size(&be.cands, get_irn_n_edges(block));
444         ir_nodeset_init_size(&be.live, get_irn_n_edges(block));
445         be.selector   = selector;
446         be.sched_env  = env;
447
448         DBG((dbg, LEVEL_1, "scheduling %+F\n", block));
449
450         if (selector->init_block)
451                 be.selector_block_env = selector->init_block(env->selector_env, block);
452
453         /* Then one can add all nodes are ready to the set. */
454         foreach_out_edge(block, edge) {
455                 ir_node *irn = get_edge_src_irn(edge);
456                 int users;
457
458                 /* Skip the end node because of keepalive edges. */
459                 if (get_irn_opcode(irn) == iro_End)
460                         continue;
461
462                 users = get_irn_n_edges(irn);
463                 if (users == 0)
464                         continue;
465                 else if (users == 1) { /* ignore nodes that are only hold by the anchor */
466                         const ir_edge_t *edge = get_irn_out_edge_first_kind(irn, EDGE_KIND_NORMAL);
467                         ir_node *user = get_edge_src_irn(edge);
468                         if (is_Anchor(user))
469                                 continue;
470                 }
471
472                 if (is_Phi(irn)) {
473                         /*
474                                 Phi functions are scheduled immediately, since they     only
475                                 transfer data flow from the predecessors to this block.
476                         */
477                         add_to_sched(&be, irn);
478                 }
479                 else if (irn == start_node) {
480                         /* The start block will be scheduled as the first node */
481                         add_to_sched(&be, irn);
482 #ifdef SCHEDULE_PROJS
483                         add_tuple_projs(&be, irn);
484 #endif
485                 }
486                 else {
487                         /* Other nodes must have all operands in other blocks to be made
488                         * ready */
489                         int ready = 1;
490
491                         /* Check, if the operands of a node are not local to this block */
492                         for (j = 0, m = get_irn_ins_or_deps(irn); j < m; ++j) {
493                                 ir_node *operand = get_irn_in_or_dep(irn, j);
494
495                                 if (get_nodes_block(operand) == block) {
496                                         ready = 0;
497                                         break;
498                                 } else {
499                                         /* live in values increase register pressure */
500                                         ir_nodeset_insert(&be.live, operand);
501                                 }
502                         }
503
504                         /* Make the node ready, if all operands live in a foreign block */
505                         if (ready) {
506                                 DBG((dbg, LEVEL_2, "\timmediately ready: %+F\n", irn));
507                                 make_ready(&be, NULL, irn);
508                         }
509                 }
510         }
511
512         /* Iterate over all remaining nodes */
513         while (ir_nodeset_size(&be.cands) > 0) {
514                 ir_nodeset_iterator_t iter;
515                 /* collect statistics about amount of ready nodes */
516                 be_do_stat_sched_ready(block, &be.cands);
517
518                 /* Keeps must be scheduled immediately */
519                 foreach_ir_nodeset(&be.cands, irn, iter) {
520                         if (be_is_Keep(irn) || be_is_CopyKeep(irn) || is_Sync(irn)) {
521                                 break;
522                         }
523                 }
524
525                 if (! irn) {
526                         /* Keeps must be immediately scheduled */
527                         irn = be.selector->select(be.selector_block_env, &be.cands, &be.live);
528                 }
529
530                 DB((dbg, LEVEL_2, "\tpicked node %+F\n", irn));
531
532                 /* Add the node to the schedule. */
533                 add_to_sched(&be, irn);
534
535                 /* remove the scheduled node from the ready list. */
536                 ir_nodeset_remove(&be.cands, irn);
537         }
538
539         if (selector->finish_block)
540                 selector->finish_block(be.selector_block_env);
541
542         ir_nodeset_destroy(&be.cands);
543         ir_nodeset_destroy(&be.live);
544 }
545
546 /* List schedule a graph. */
547 void list_sched(be_irg_t *birg, be_options_t *be_opts)
548 {
549         const arch_env_t *arch_env = birg->main_env->arch_env;
550         ir_graph         *irg      = birg->irg;
551
552         int num_nodes;
553         sched_env_t env;
554         mris_env_t *mris = NULL;
555         list_sched_selector_t sel;
556
557         (void)be_opts;
558
559         /* Select a scheduler based on backend options */
560         switch (list_sched_options.select) {
561                 case BE_SCHED_SELECT_TRIVIAL:  sel = trivial_selector;      break;
562                 case BE_SCHED_SELECT_RANDOM:   sel = random_selector;       break;
563                 case BE_SCHED_SELECT_REGPRESS: sel = reg_pressure_selector; break;
564                 case BE_SCHED_SELECT_MUCHNIK:  sel = muchnik_selector;      break;
565                 case BE_SCHED_SELECT_HEUR:     sel = heuristic_selector;    break;
566                 case BE_SCHED_SELECT_NORMAL:   sel = normal_selector;       break;
567                 default:
568                 case BE_SCHED_SELECT_HMUCHNIK: sel = trivial_selector;      break;
569         }
570
571 #if 1
572         /* Matze: This is very slow, we should avoid it to improve backend speed,
573          * we just have to make sure that we have no dangling out-edges at this
574          * point...
575          */
576
577         /* Assure, that we have no dangling out-edges to deleted stuff */
578         edges_deactivate(birg->irg);
579         edges_activate(birg->irg);
580 #endif
581
582         switch (list_sched_options.prep) {
583                 case BE_SCHED_PREP_MRIS:
584                         mris = be_sched_mris_preprocess(birg);
585                         break;
586                 case BE_SCHED_PREP_RSS:
587                         rss_schedule_preparation(birg);
588                         break;
589                 default:
590                         break;
591         }
592
593         num_nodes = get_irg_last_idx(irg);
594
595         /* initialize environment for list scheduler */
596         memset(&env, 0, sizeof(env));
597         env.selector   = arch_env->isa->impl->get_list_sched_selector(arch_env->isa, &sel);
598         env.arch_env   = arch_env;
599         env.irg        = irg;
600         env.sched_info = NEW_ARR_F(sched_irn_t, num_nodes);
601
602         memset(env.sched_info, 0, num_nodes * sizeof(env.sched_info[0]));
603
604         if (env.selector->init_graph)
605                 env.selector_env = env.selector->init_graph(env.selector, birg);
606
607         /* Schedule each single block. */
608         irg_block_walk_graph(irg, list_sched_block, NULL, &env);
609
610         if (env.selector->finish_graph)
611                 env.selector->finish_graph(env.selector_env);
612
613         if (list_sched_options.prep == BE_SCHED_PREP_MRIS)
614                 be_sched_mris_free(mris);
615
616         DEL_ARR_F(env.sched_info);
617 }
618
619 /* List schedule a block. */
620 void list_sched_single_block(const be_irg_t *birg, ir_node *block,
621                              be_options_t *be_opts)
622 {
623         const arch_env_t *arch_env = birg->main_env->arch_env;
624         ir_graph         *irg      = birg->irg;
625
626         int num_nodes;
627         sched_env_t env;
628         list_sched_selector_t sel;
629
630         (void)be_opts;
631
632         /* Select a scheduler based on backend options */
633         switch (list_sched_options.select) {
634                 case BE_SCHED_SELECT_TRIVIAL:  sel = trivial_selector;      break;
635                 case BE_SCHED_SELECT_RANDOM:   sel = random_selector;       break;
636                 case BE_SCHED_SELECT_REGPRESS: sel = reg_pressure_selector; break;
637                 case BE_SCHED_SELECT_MUCHNIK:  sel = muchnik_selector;      break;
638                 case BE_SCHED_SELECT_HEUR:     sel = heuristic_selector;    break;
639                 case BE_SCHED_SELECT_NORMAL:   sel = normal_selector;       break;
640                 default:
641                 case BE_SCHED_SELECT_HMUCHNIK: sel = trivial_selector;      break;
642         }
643
644         /* Assure, that the out edges are computed */
645         edges_deactivate(birg->irg);
646         edges_activate(birg->irg);
647
648         num_nodes = get_irg_last_idx(irg);
649
650         /* initialize environment for list scheduler */
651         memset(&env, 0, sizeof(env));
652         env.selector   = arch_env->isa->impl->get_list_sched_selector(arch_env->isa, &sel);
653         env.arch_env   = arch_env;
654         env.irg        = irg;
655         env.sched_info = NEW_ARR_F(sched_irn_t, num_nodes);
656
657         memset(env.sched_info, 0, num_nodes * sizeof(env.sched_info[0]));
658
659         if (env.selector->init_graph)
660                 env.selector_env = env.selector->init_graph(env.selector, birg);
661
662         /* Schedule block. */
663         list_sched_block(block, &env);
664
665         if (env.selector->finish_graph)
666                 env.selector->finish_graph(env.selector_env);
667
668         DEL_ARR_F(env.sched_info);
669 }
670
671 /**
672  * Register list scheduler options.
673  */
674 void be_init_listsched(void) {
675         lc_opt_entry_t *be_grp = lc_opt_get_grp(firm_opt_get_root(), "be");
676         lc_opt_entry_t *sched_grp = lc_opt_get_grp(be_grp, "listsched");
677
678         lc_opt_add_table(sched_grp, list_sched_option_table);
679
680         FIRM_DBG_REGISTER(dbg, "firm.be.sched");
681 }
682
683 BE_REGISTER_MODULE_CONSTRUCTOR(be_init_listsched);