]> git.ipfire.org Git - thirdparty/gcc.git/blame - gcc/graphite.c
[Ada] Use Standard.Natural on bit references to packed arrays
[thirdparty/gcc.git] / gcc / graphite.c
CommitLineData
f8bf9252 1/* Gimple Represented as Polyhedra.
8d9254fc 2 Copyright (C) 2006-2020 Free Software Foundation, Inc.
f8bf9252
SP
3 Contributed by Sebastian Pop <sebastian.pop@inria.fr>.
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 3, or (at your option)
10any later version.
11
12GCC is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with GCC; see the file COPYING3. If not see
19<http://www.gnu.org/licenses/>. */
20
21/* This pass converts GIMPLE to GRAPHITE, performs some loop
22 transformations and then converts the resulting representation back
204b560f 23 to GIMPLE.
f8bf9252
SP
24
25 An early description of this pass can be found in the GCC Summit'06
26 paper "GRAPHITE: Polyhedral Analyses and Optimizations for GCC".
27 The wiki page http://gcc.gnu.org/wiki/Graphite contains pointers to
d6bb5ccf 28 the related work. */
f8bf9252 29
4d776011 30#define USES_ISL
33ad93b9 31
4d776011 32#include "config.h"
f8bf9252
SP
33#include "system.h"
34#include "coretypes.h"
c7131fb2 35#include "backend.h"
9c358739
AM
36#include "diagnostic-core.h"
37#include "cfgloop.h"
38#include "tree-pass.h"
7009b073 39#include "pretty-print.h"
b25f84d0 40#include "cfganal.h"
9c358739
AM
41
42#ifdef HAVE_isl
9fdcd34e 43#include "cfghooks.h"
40e23961 44#include "tree.h"
c7131fb2 45#include "gimple.h"
ab0e5308 46#include "ssa.h"
c7131fb2 47#include "fold-const.h"
5be5c238 48#include "gimple-iterator.h"
442b4905
AM
49#include "tree-cfg.h"
50#include "tree-ssa-loop.h"
f8bf9252
SP
51#include "tree-data-ref.h"
52#include "tree-scalar-evolution.h"
6a7441f5 53#include "dbgcnt.h"
c1bf2a39 54#include "tree-parloops.h"
4484a35a 55#include "tree-cfgcleanup.h"
558b3185 56#include "tree-vectorizer.h"
ab0e5308 57#include "tree-ssa-loop-manip.h"
6fe00fb7
RB
58#include "tree-ssa.h"
59#include "tree-into-ssa.h"
cf98f0f4 60#include "graphite.h"
57d598f7 61
204b560f 62/* Print global statistics to FILE. */
4d6c7237
SP
63
64static void
204b560f 65print_global_statistics (FILE* file)
4d6c7237 66{
204b560f
SP
67 long n_bbs = 0;
68 long n_loops = 0;
69 long n_stmts = 0;
70 long n_conditions = 0;
3995f3a2
JH
71 profile_count n_p_bbs = profile_count::zero ();
72 profile_count n_p_loops = profile_count::zero ();
73 profile_count n_p_stmts = profile_count::zero ();
74 profile_count n_p_conditions = profile_count::zero ();
4d6c7237 75
204b560f 76 basic_block bb;
4d6c7237 77
04a90bec 78 FOR_ALL_BB_FN (bb, cfun)
204b560f
SP
79 {
80 gimple_stmt_iterator psi;
4d6c7237 81
204b560f 82 n_bbs++;
3995f3a2
JH
83 if (bb->count.initialized_p ())
84 n_p_bbs += bb->count;
4d6c7237 85
204b560f
SP
86 /* Ignore artificial surrounding loop. */
87 if (bb == bb->loop_father->header
88 && bb->index != 0)
4d6c7237 89 {
204b560f
SP
90 n_loops++;
91 n_p_loops += bb->count;
4d6c7237 92 }
4d6c7237 93
d630245f 94 if (EDGE_COUNT (bb->succs) > 1)
204b560f
SP
95 {
96 n_conditions++;
3995f3a2
JH
97 if (bb->count.initialized_p ())
98 n_p_conditions += bb->count;
204b560f 99 }
4d6c7237 100
204b560f
SP
101 for (psi = gsi_start_bb (bb); !gsi_end_p (psi); gsi_next (&psi))
102 {
103 n_stmts++;
3995f3a2
JH
104 if (bb->count.initialized_p ())
105 n_p_stmts += bb->count;
204b560f 106 }
4d6c7237
SP
107 }
108
204b560f
SP
109 fprintf (file, "\nGlobal statistics (");
110 fprintf (file, "BBS:%ld, ", n_bbs);
111 fprintf (file, "LOOPS:%ld, ", n_loops);
112 fprintf (file, "CONDITIONS:%ld, ", n_conditions);
113 fprintf (file, "STMTS:%ld)\n", n_stmts);
c46bd472 114 fprintf (file, "Global profiling statistics (");
3995f3a2
JH
115 fprintf (file, "BBS:");
116 n_p_bbs.dump (file);
117 fprintf (file, ", LOOPS:");
118 n_p_loops.dump (file);
119 fprintf (file, ", CONDITIONS:");
120 n_p_conditions.dump (file);
121 fprintf (file, ", STMTS:");
122 n_p_stmts.dump (file);
c46bd472 123 fprintf (file, ")\n\n");
f8bf9252
SP
124}
125
204b560f 126/* Print statistics for SCOP to FILE. */
f8bf9252
SP
127
128static void
204b560f 129print_graphite_scop_statistics (FILE* file, scop_p scop)
f8bf9252 130{
204b560f
SP
131 long n_bbs = 0;
132 long n_loops = 0;
133 long n_stmts = 0;
134 long n_conditions = 0;
3995f3a2
JH
135 profile_count n_p_bbs = profile_count::zero ();
136 profile_count n_p_loops = profile_count::zero ();
137 profile_count n_p_stmts = profile_count::zero ();
138 profile_count n_p_conditions = profile_count::zero ();
f8bf9252 139
204b560f 140 basic_block bb;
f8bf9252 141
04a90bec 142 FOR_ALL_BB_FN (bb, cfun)
f8bf9252 143 {
204b560f
SP
144 gimple_stmt_iterator psi;
145 loop_p loop = bb->loop_father;
f8bf9252 146
d37fc3aa 147 if (!bb_in_sese_p (bb, scop->scop_info->region))
204b560f 148 continue;
f8bf9252 149
204b560f 150 n_bbs++;
3995f3a2
JH
151 if (bb->count.initialized_p ())
152 n_p_bbs += bb->count;
f8bf9252 153
d630245f 154 if (EDGE_COUNT (bb->succs) > 1)
f8bf9252 155 {
204b560f
SP
156 n_conditions++;
157 n_p_conditions += bb->count;
f8bf9252 158 }
f8bf9252 159
204b560f 160 for (psi = gsi_start_bb (bb); !gsi_end_p (psi); gsi_next (&psi))
f8bf9252 161 {
204b560f
SP
162 n_stmts++;
163 n_p_stmts += bb->count;
f8bf9252 164 }
f8bf9252 165
d37fc3aa 166 if (loop->header == bb && loop_in_sese_p (loop, scop->scop_info->region))
204b560f
SP
167 {
168 n_loops++;
169 n_p_loops += bb->count;
170 }
171 }
6a114766 172
7009b073
SP
173 fprintf (file, "\nFunction Name: %s\n", current_function_name ());
174
d37fc3aa
AK
175 edge scop_begin = scop->scop_info->region.entry;
176 edge scop_end = scop->scop_info->region.exit;
7009b073
SP
177
178 fprintf (file, "\nSCoP (entry_edge (bb_%d, bb_%d), ",
179 scop_begin->src->index, scop_begin->dest->index);
180 fprintf (file, "exit_edge (bb_%d, bb_%d))",
181 scop_end->src->index, scop_end->dest->index);
182
204b560f
SP
183 fprintf (file, "\nSCoP statistics (");
184 fprintf (file, "BBS:%ld, ", n_bbs);
185 fprintf (file, "LOOPS:%ld, ", n_loops);
186 fprintf (file, "CONDITIONS:%ld, ", n_conditions);
187 fprintf (file, "STMTS:%ld)\n", n_stmts);
c46bd472 188 fprintf (file, "SCoP profiling statistics (");
3995f3a2
JH
189 fprintf (file, "BBS:");
190 n_p_bbs.dump (file);
191 fprintf (file, ", LOOPS:");
192 n_p_loops.dump (file);
193 fprintf (file, ", CONDITIONS:");
194 n_p_conditions.dump (file);
195 fprintf (file, ", STMTS:");
196 n_p_stmts.dump (file);
c46bd472 197 fprintf (file, ")\n\n");
f8bf9252
SP
198}
199
204b560f 200/* Print statistics for SCOPS to FILE. */
f8bf9252 201
204b560f 202static void
9771b263 203print_graphite_statistics (FILE* file, vec<scop_p> scops)
f8bf9252 204{
f8bf9252 205 int i;
204b560f 206 scop_p scop;
f8bf9252 207
9771b263 208 FOR_EACH_VEC_ELT (scops, i, scop)
204b560f 209 print_graphite_scop_statistics (file, scop);
f8bf9252
SP
210}
211
124f4f57
RB
212struct seir_cache_key
213{
214 hashval_t hash;
215 int entry_dest;
216 int exit_src;
217 int loop_num;
218 tree expr;
219};
220
221struct sese_scev_hash : typed_noop_remove <seir_cache_key>
222{
223 typedef seir_cache_key value_type;
224 typedef seir_cache_key compare_type;
225 static hashval_t hash (const seir_cache_key &key) { return key.hash; }
226 static bool
227 equal (const seir_cache_key &key1, const seir_cache_key &key2)
228 {
229 return (key1.hash == key2.hash
230 && key1.entry_dest == key2.entry_dest
231 && key1.exit_src == key2.exit_src
232 && key1.loop_num == key2.loop_num
233 && operand_equal_p (key1.expr, key2.expr, 0));
234 }
235 static void mark_deleted (seir_cache_key &key) { key.expr = NULL_TREE; }
7ca50de0 236 static const bool empty_zero_p = false;
124f4f57
RB
237 static void mark_empty (seir_cache_key &key) { key.entry_dest = 0; }
238 static bool is_deleted (const seir_cache_key &key) { return !key.expr; }
239 static bool is_empty (const seir_cache_key &key) { return key.entry_dest == 0; }
240};
241
242static hash_map<sese_scev_hash, tree> *seir_cache;
243
244/* Same as scalar_evolution_in_region but caches results so we avoid
245 re-computing evolutions during transform phase. */
246
247tree
248cached_scalar_evolution_in_region (const sese_l &region, loop_p loop,
249 tree expr)
250{
251 seir_cache_key key;
252 key.entry_dest = region.entry->dest->index;
253 key.exit_src = region.exit->src->index;
254 key.loop_num = loop->num;
255 key.expr = expr;
256 inchash::hash hstate (0);
257 hstate.add_int (key.entry_dest);
258 hstate.add_int (key.exit_src);
259 hstate.add_int (key.loop_num);
260 inchash::add_expr (key.expr, hstate);
261 key.hash = hstate.end ();
262
263 bool existed;
264 tree &chrec = seir_cache->get_or_insert (key, &existed);
265 if (!existed)
266 chrec = scalar_evolution_in_region (region, loop, expr);
267 return chrec;
268}
269
87ccab5d
AK
270/* Deletes all scops in SCOPS. */
271
272static void
273free_scops (vec<scop_p> scops)
274{
275 int i;
276 scop_p scop;
277
278 FOR_EACH_VEC_ELT (scops, i, scop)
279 free_scop (scop);
280
281 scops.release ();
282}
283
ab0e5308
RB
284/* Transforms LOOP to the canonical loop closed SSA form. */
285
286static void
82c066f5 287canonicalize_loop_closed_ssa (loop_p loop, edge e)
ab0e5308 288{
ab0e5308 289 basic_block bb;
4d6e2f33 290 gphi_iterator psi;
ab0e5308 291
ab0e5308
RB
292 bb = e->dest;
293
4d6e2f33
RB
294 /* Make the loop-close PHI node BB contain only PHIs and have a
295 single predecessor. */
ab0e5308
RB
296 if (single_pred_p (bb))
297 {
298 e = split_block_after_labels (bb);
4d6e2f33 299 bb = e->src;
ab0e5308
RB
300 }
301 else
302 {
ab0e5308
RB
303 basic_block close = split_edge (e);
304 e = single_succ_edge (close);
305 for (psi = gsi_start_phis (bb); !gsi_end_p (psi); gsi_next (&psi))
306 {
307 gphi *phi = psi.phi ();
308 use_operand_p use_p = PHI_ARG_DEF_PTR_FROM_EDGE (phi, e);
309 tree arg = USE_FROM_PTR (use_p);
310
311 /* Only add close phi nodes for SSA_NAMEs defined in LOOP. */
312 if (TREE_CODE (arg) != SSA_NAME
621e5370
RB
313 || SSA_NAME_IS_DEFAULT_DEF (arg)
314 || ! flow_bb_inside_loop_p (loop,
315 gimple_bb (SSA_NAME_DEF_STMT (arg))))
ab0e5308
RB
316 continue;
317
318 tree res = copy_ssa_name (arg);
319 gphi *close_phi = create_phi_node (res, close);
320 add_phi_arg (close_phi, arg, gimple_phi_arg_edge (close_phi, 0),
321 UNKNOWN_LOCATION);
322 SET_USE (use_p, res);
323 }
4d6e2f33
RB
324 bb = close;
325 }
326
327 /* Eliminate duplicates. This relies on processing loops from
328 innermost to outer. */
329 for (psi = gsi_start_phis (bb); !gsi_end_p (psi); gsi_next (&psi))
330 {
331 gphi_iterator gsi = psi;
332 gphi *phi = psi.phi ();
333
334 /* At this point, PHI should be a close phi in normal form. */
335 gcc_assert (gimple_phi_num_args (phi) == 1);
ab0e5308 336
4d6e2f33
RB
337 /* Iterate over the next phis and remove duplicates. */
338 gsi_next (&gsi);
339 while (!gsi_end_p (gsi))
340 if (gimple_phi_arg_def (phi, 0) == gimple_phi_arg_def (gsi.phi (), 0))
341 {
342 replace_uses_by (gimple_phi_result (gsi.phi ()),
343 gimple_phi_result (phi));
344 remove_phi_node (&gsi, true);
345 }
346 else
347 gsi_next (&gsi);
ab0e5308
RB
348 }
349}
350
351/* Converts the current loop closed SSA form to a canonical form
352 expected by the Graphite code generation.
353
354 The loop closed SSA form has the following invariant: a variable
355 defined in a loop that is used outside the loop appears only in the
356 phi nodes in the destination of the loop exit. These phi nodes are
357 called close phi nodes.
358
359 The canonical loop closed SSA form contains the extra invariants:
360
361 - when the loop contains only one exit, the close phi nodes contain
362 only one argument. That implies that the basic block that contains
363 the close phi nodes has only one predecessor, that is a basic block
364 in the loop.
365
366 - the basic block containing the close phi nodes does not contain
367 other statements.
368
369 - there exist only one phi node per definition in the loop.
82c066f5
RB
370
371 In addition to that we also make sure that loop exit edges are
372 first in the successor edge vector. This is to make RPO order
373 as computed by pre_and_rev_post_order_compute be consistent with
374 what initial schedule generation expects.
ab0e5308
RB
375*/
376
377static void
82c066f5 378canonicalize_loop_form (void)
ab0e5308
RB
379{
380 loop_p loop;
4d6e2f33 381 FOR_EACH_LOOP (loop, LI_FROM_INNERMOST)
82c066f5
RB
382 {
383 edge e = single_exit (loop);
b0bd3e52 384 if (!e || (e->flags & (EDGE_COMPLEX|EDGE_FAKE)))
82c066f5
RB
385 continue;
386
387 canonicalize_loop_closed_ssa (loop, e);
388
389 /* If the exit is not first in the edge vector make it so. */
390 if (e != EDGE_SUCC (e->src, 0))
391 {
392 unsigned ei;
393 for (ei = 0; EDGE_SUCC (e->src, ei) != e; ++ei)
394 ;
395 std::swap (EDGE_SUCC (e->src, ei), EDGE_SUCC (e->src, 0));
396 }
397 }
ab0e5308 398
b33086c0
RB
399 /* We can end up releasing duplicate exit PHIs and also introduce
400 additional copies so the cached information isn't correct anymore. */
401 scev_reset ();
402
ab0e5308
RB
403 checking_verify_loop_closed_ssa (true);
404}
405
33ad93b9
RG
406isl_ctx *the_isl_ctx;
407
f8bf9252
SP
408/* Perform a set of linear transforms on the loops of the current
409 function. */
410
411void
412graphite_transform_loops (void)
413{
414 int i;
415 scop_p scop;
6fe00fb7 416 bool changed = false;
6e1aa848 417 vec<scop_p> scops = vNULL;
33ad93b9 418 isl_ctx *ctx;
f8bf9252 419
62e0a1ed
RG
420 /* If a function is parallel it was most probably already run through graphite
421 once. No need to run again. */
422 if (parallelized_function_p (cfun->decl))
423 return;
424
6fe00fb7 425 calculate_dominance_info (CDI_DOMINATORS);
f8bf9252 426
b25f84d0
RB
427 /* We rely on post-dominators during merging of SESE regions so those
428 have to be meaningful. */
429 connect_infinite_loops_to_exit ();
430
ab0e5308
RB
431 ctx = isl_ctx_alloc ();
432 isl_options_set_on_error (ctx, ISL_ON_ERROR_ABORT);
33ad93b9 433 the_isl_ctx = ctx;
ab0e5308 434
26993e95 435 sort_sibling_loops (cfun);
82c066f5 436 canonicalize_loop_form ();
ab0e5308 437
c46bd472
RB
438 /* Print the loop structure. */
439 if (dump_file && (dump_flags & TDF_DETAILS))
440 {
441 print_loops (dump_file, 2);
442 print_loops (dump_file, 3);
443 }
444
124f4f57
RB
445 seir_cache = new hash_map<sese_scev_hash, tree>;
446
ab0e5308 447 calculate_dominance_info (CDI_POST_DOMINATORS);
204b560f 448 build_scops (&scops);
ab0e5308 449 free_dominance_info (CDI_POST_DOMINATORS);
f8bf9252 450
b25f84d0
RB
451 /* Remove the fake exits before transform given they are not reflected
452 in loop structures we end up verifying. */
453 remove_fake_exit_edges ();
454
f8bf9252 455 if (dump_file && (dump_flags & TDF_DETAILS))
f8bf9252 456 {
204b560f
SP
457 print_graphite_statistics (dump_file, scops);
458 print_global_statistics (dump_file);
459 }
0b040072 460
9771b263 461 FOR_EACH_VEC_ELT (scops, i, scop)
6a7441f5
SP
462 if (dbg_cnt (graphite_scop))
463 {
8e4dc590 464 scop->isl_context = ctx;
36f40be0
AK
465 if (!build_poly_scop (scop))
466 continue;
467
468 if (!apply_poly_transforms (scop))
469 continue;
470
6fe00fb7 471 changed = true;
bbeeac91
DM
472 if (graphite_regenerate_ast_isl (scop)
473 && dump_enabled_p ())
e33507e3 474 {
4f5b9c80 475 dump_user_location_t loc = find_loop_location
e33507e3
RB
476 (scops[i]->scop_info->region.entry->dest->loop_father);
477 dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
478 "loop nest optimized\n");
479 }
efa21390 480 }
f8bf9252 481
124f4f57
RB
482 delete seir_cache;
483 seir_cache = NULL;
484
6fe00fb7
RB
485 if (changed)
486 {
487 mark_virtual_operands_for_renaming (cfun);
488 update_ssa (TODO_update_ssa);
489 checking_verify_ssa (true, true);
490 rewrite_into_loop_closed_ssa (NULL, 0);
491 scev_reset ();
492 checking_verify_loop_structure ();
493 }
494
4d6e2f33
RB
495 if (dump_file && (dump_flags & TDF_DETAILS))
496 {
497 loop_p loop;
498 int num_no_dependency = 0;
499
500 FOR_EACH_LOOP (loop, 0)
501 if (loop->can_be_parallel)
502 num_no_dependency++;
503
504 fprintf (dump_file, "%d loops carried no dependency.\n",
505 num_no_dependency);
506 }
507
204b560f 508 free_scops (scops);
33ad93b9
RG
509 the_isl_ctx = NULL;
510 isl_ctx_free (ctx);
6fe00fb7
RB
511
512 if (changed)
513 {
514 cleanup_tree_cfg ();
515 profile_status_for_fn (cfun) = PROFILE_ABSENT;
516 release_recorded_exits (cfun);
517 tree_estimate_probability (false);
518 }
f8bf9252
SP
519}
520
e357a5e0 521#else /* If isl is not available: #ifndef HAVE_isl. */
f8bf9252 522
c1bf2a39 523static void
f8bf9252
SP
524graphite_transform_loops (void)
525{
e357a5e0 526 sorry ("Graphite loop optimizations cannot be used (isl is not available).");
f8bf9252
SP
527}
528
529#endif
c1bf2a39
AM
530
531
532static unsigned int
726338f4 533graphite_transforms (struct function *fun)
c1bf2a39 534{
726338f4 535 if (number_of_loops (fun) <= 1)
c1bf2a39
AM
536 return 0;
537
538 graphite_transform_loops ();
539
540 return 0;
541}
542
543static bool
544gate_graphite_transforms (void)
545{
546 /* Enable -fgraphite pass if any one of the graphite optimization flags
547 is turned on. */
d6bb5ccf 548 if (flag_graphite_identity
c1bf2a39 549 || flag_loop_parallelize_all
84c36240 550 || flag_loop_nest_optimize)
c1bf2a39
AM
551 flag_graphite = 1;
552
553 return flag_graphite != 0;
554}
555
17795822
TS
556namespace {
557
558const pass_data pass_data_graphite =
c1bf2a39
AM
559{
560 GIMPLE_PASS, /* type */
561 "graphite0", /* name */
562 OPTGROUP_LOOP, /* optinfo_flags */
c1bf2a39
AM
563 TV_GRAPHITE, /* tv_id */
564 ( PROP_cfg | PROP_ssa ), /* properties_required */
565 0, /* properties_provided */
566 0, /* properties_destroyed */
567 0, /* todo_flags_start */
568 0, /* todo_flags_finish */
569};
570
17795822 571class pass_graphite : public gimple_opt_pass
c1bf2a39
AM
572{
573public:
574 pass_graphite (gcc::context *ctxt)
575 : gimple_opt_pass (pass_data_graphite, ctxt)
576 {}
577
578 /* opt_pass methods: */
1a3d085c 579 virtual bool gate (function *) { return gate_graphite_transforms (); }
c1bf2a39
AM
580
581}; // class pass_graphite
582
17795822
TS
583} // anon namespace
584
c1bf2a39
AM
585gimple_opt_pass *
586make_pass_graphite (gcc::context *ctxt)
587{
588 return new pass_graphite (ctxt);
589}
590
17795822
TS
591namespace {
592
593const pass_data pass_data_graphite_transforms =
c1bf2a39
AM
594{
595 GIMPLE_PASS, /* type */
596 "graphite", /* name */
597 OPTGROUP_LOOP, /* optinfo_flags */
c1bf2a39
AM
598 TV_GRAPHITE_TRANSFORMS, /* tv_id */
599 ( PROP_cfg | PROP_ssa ), /* properties_required */
600 0, /* properties_provided */
601 0, /* properties_destroyed */
602 0, /* todo_flags_start */
603 0, /* todo_flags_finish */
604};
605
17795822 606class pass_graphite_transforms : public gimple_opt_pass
c1bf2a39
AM
607{
608public:
609 pass_graphite_transforms (gcc::context *ctxt)
610 : gimple_opt_pass (pass_data_graphite_transforms, ctxt)
611 {}
612
613 /* opt_pass methods: */
1a3d085c 614 virtual bool gate (function *) { return gate_graphite_transforms (); }
726338f4 615 virtual unsigned int execute (function *fun) { return graphite_transforms (fun); }
c1bf2a39
AM
616
617}; // class pass_graphite_transforms
618
17795822
TS
619} // anon namespace
620
c1bf2a39
AM
621gimple_opt_pass *
622make_pass_graphite_transforms (gcc::context *ctxt)
623{
624 return new pass_graphite_transforms (ctxt);
625}
626
627