]> git.ipfire.org Git - thirdparty/gcc.git/blob - gcc/config/nds32/nds32-isr.c
coretypes.h: Include input.h and as-a.h.
[thirdparty/gcc.git] / gcc / config / nds32 / nds32-isr.c
1 /* Subroutines used for ISR of Andes NDS32 cpu for GNU compiler
2 Copyright (C) 2012-2015 Free Software Foundation, Inc.
3 Contributed by Andes Technology Corporation.
4
5 This file is part of GCC.
6
7 GCC is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published
9 by the Free Software Foundation; either version 3, or (at your
10 option) any later version.
11
12 GCC is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
15 License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GCC; see the file COPYING3. If not see
19 <http://www.gnu.org/licenses/>. */
20
21 /* ------------------------------------------------------------------------ */
22
23 #include "config.h"
24 #include "system.h"
25 #include "coretypes.h"
26 #include "tm.h"
27 #include "alias.h"
28 #include "symtab.h"
29 #include "tree.h"
30 #include "stor-layout.h"
31 #include "varasm.h"
32 #include "calls.h"
33 #include "rtl.h"
34 #include "regs.h"
35 #include "hard-reg-set.h"
36 #include "insn-config.h" /* Required by recog.h. */
37 #include "conditions.h"
38 #include "output.h"
39 #include "insn-attr.h" /* For DFA state_t. */
40 #include "insn-codes.h" /* For CODE_FOR_xxx. */
41 #include "reload.h" /* For push_reload(). */
42 #include "flags.h"
43 #include "function.h"
44 #include "insn-config.h"
45 #include "expmed.h"
46 #include "dojump.h"
47 #include "explow.h"
48 #include "emit-rtl.h"
49 #include "stmt.h"
50 #include "expr.h"
51 #include "recog.h"
52 #include "diagnostic-core.h"
53 #include "dominance.h"
54 #include "cfg.h"
55 #include "cfgrtl.h"
56 #include "cfganal.h"
57 #include "lcm.h"
58 #include "cfgbuild.h"
59 #include "cfgcleanup.h"
60 #include "predict.h"
61 #include "basic-block.h"
62 #include "df.h"
63 #include "tm_p.h"
64 #include "tm-constrs.h"
65 #include "optabs.h" /* For GEN_FCN. */
66 #include "target.h"
67 #include "target-def.h"
68 #include "langhooks.h" /* For add_builtin_function(). */
69 #include "builtins.h"
70
71 /* ------------------------------------------------------------------------ */
72
73 /* Refer to nds32.h, there are maximum 73 isr vectors in nds32 architecture.
74 0 for reset handler with __attribute__((reset())),
75 1-8 for exception handler with __attribute__((exception(1,...,8))),
76 and 9-72 for interrupt handler with __attribute__((interrupt(0,...,63))).
77 We use an array to record essential information for each vector. */
78 static struct nds32_isr_info nds32_isr_vectors[NDS32_N_ISR_VECTORS];
79
80 /* ------------------------------------------------------------------------ */
81
82 /* A helper function to emit section head template. */
83 static void
84 nds32_emit_section_head_template (char section_name[],
85 char symbol_name[],
86 int align_value,
87 bool object_p)
88 {
89 const char *flags_str;
90 const char *type_str;
91
92 flags_str = (object_p) ? "\"a\"" : "\"ax\"";
93 type_str = (object_p) ? "@object" : "@function";
94
95 fprintf (asm_out_file, "\t.section\t%s, %s\n", section_name, flags_str);
96 fprintf (asm_out_file, "\t.align\t%d\n", align_value);
97 fprintf (asm_out_file, "\t.global\t%s\n", symbol_name);
98 fprintf (asm_out_file, "\t.type\t%s, %s\n", symbol_name, type_str);
99 fprintf (asm_out_file, "%s:\n", symbol_name);
100 }
101
102 /* A helper function to emit section tail template. */
103 static void
104 nds32_emit_section_tail_template (char symbol_name[])
105 {
106 fprintf (asm_out_file, "\t.size\t%s, .-%s\n", symbol_name, symbol_name);
107 }
108
109 /* Function to emit isr jump table section. */
110 static void
111 nds32_emit_isr_jmptbl_section (int vector_id)
112 {
113 char section_name[100];
114 char symbol_name[100];
115
116 /* Prepare jmptbl section and symbol name. */
117 snprintf (section_name, sizeof (section_name),
118 ".nds32_jmptbl.%02d", vector_id);
119 snprintf (symbol_name, sizeof (symbol_name),
120 "_nds32_jmptbl_%02d", vector_id);
121
122 nds32_emit_section_head_template (section_name, symbol_name, 2, true);
123 fprintf (asm_out_file, "\t.word\t%s\n",
124 nds32_isr_vectors[vector_id].func_name);
125 nds32_emit_section_tail_template (symbol_name);
126 }
127
128 /* Function to emit isr vector section. */
129 static void
130 nds32_emit_isr_vector_section (int vector_id)
131 {
132 unsigned int vector_number_offset = 0;
133 const char *c_str = "CATEGORY";
134 const char *sr_str = "SR";
135 const char *nt_str = "NT";
136 const char *vs_str = "VS";
137 char first_level_handler_name[100];
138 char section_name[100];
139 char symbol_name[100];
140
141 /* Set the vector number offset so that we can calculate
142 the value that user specifies in the attribute.
143 We also prepare the category string for first level handler name. */
144 switch (nds32_isr_vectors[vector_id].category)
145 {
146 case NDS32_ISR_INTERRUPT:
147 vector_number_offset = 9;
148 c_str = "i";
149 break;
150 case NDS32_ISR_EXCEPTION:
151 vector_number_offset = 0;
152 c_str = "e";
153 break;
154 case NDS32_ISR_NONE:
155 case NDS32_ISR_RESET:
156 /* Normally it should not be here. */
157 gcc_unreachable ();
158 break;
159 }
160
161 /* Prepare save reg string for first level handler name. */
162 switch (nds32_isr_vectors[vector_id].save_reg)
163 {
164 case NDS32_SAVE_ALL:
165 sr_str = "sa";
166 break;
167 case NDS32_PARTIAL_SAVE:
168 sr_str = "ps";
169 break;
170 }
171
172 /* Prepare nested type string for first level handler name. */
173 switch (nds32_isr_vectors[vector_id].nested_type)
174 {
175 case NDS32_NESTED:
176 nt_str = "ns";
177 break;
178 case NDS32_NOT_NESTED:
179 nt_str = "nn";
180 break;
181 case NDS32_NESTED_READY:
182 nt_str = "nr";
183 break;
184 }
185
186 /* Currently we have 4-byte or 16-byte size for each vector.
187 If it is 4-byte, the first level handler name has suffix string "_4b". */
188 vs_str = (nds32_isr_vector_size == 4) ? "_4b" : "";
189
190 /* Now we can create first level handler name. */
191 snprintf (first_level_handler_name, sizeof (first_level_handler_name),
192 "_nds32_%s_%s_%s%s", c_str, sr_str, nt_str, vs_str);
193
194 /* Prepare vector section and symbol name. */
195 snprintf (section_name, sizeof (section_name),
196 ".nds32_vector.%02d", vector_id);
197 snprintf (symbol_name, sizeof (symbol_name),
198 "_nds32_vector_%02d%s", vector_id, vs_str);
199
200
201 /* Everything is ready. We can start emit vector section content. */
202 nds32_emit_section_head_template (section_name, symbol_name,
203 floor_log2 (nds32_isr_vector_size), false);
204
205 /* According to the vector size, the instructions in the
206 vector section may be different. */
207 if (nds32_isr_vector_size == 4)
208 {
209 /* This block is for 4-byte vector size.
210 Hardware $VID support is necessary and only one instruction
211 is needed in vector section. */
212 fprintf (asm_out_file, "\tj\t%s ! jump to first level handler\n",
213 first_level_handler_name);
214 }
215 else
216 {
217 /* This block is for 16-byte vector size.
218 There is NO hardware $VID so that we need several instructions
219 such as pushing GPRs and preparing software vid at vector section.
220 For pushing GPRs, there are four variations for
221 16-byte vector content and we have to handle each combination.
222 For preparing software vid, note that the vid need to
223 be substracted vector_number_offset. */
224 if (TARGET_REDUCED_REGS)
225 {
226 if (nds32_isr_vectors[vector_id].save_reg == NDS32_SAVE_ALL)
227 {
228 /* Case of reduced set registers and save_all attribute. */
229 fprintf (asm_out_file, "\t! reduced set regs + save_all\n");
230 fprintf (asm_out_file, "\tsmw.adm\t$r15, [$sp], $r15, 0xf\n");
231 fprintf (asm_out_file, "\tsmw.adm\t$r0, [$sp], $r10, 0x0\n");
232
233 }
234 else
235 {
236 /* Case of reduced set registers and partial_save attribute. */
237 fprintf (asm_out_file, "\t! reduced set regs + partial_save\n");
238 fprintf (asm_out_file, "\tsmw.adm\t$r15, [$sp], $r15, 0x2\n");
239 fprintf (asm_out_file, "\tsmw.adm\t$r0, [$sp], $r5, 0x0\n");
240 }
241 }
242 else
243 {
244 if (nds32_isr_vectors[vector_id].save_reg == NDS32_SAVE_ALL)
245 {
246 /* Case of full set registers and save_all attribute. */
247 fprintf (asm_out_file, "\t! full set regs + save_all\n");
248 fprintf (asm_out_file, "\tsmw.adm\t$r0, [$sp], $r27, 0xf\n");
249 }
250 else
251 {
252 /* Case of full set registers and partial_save attribute. */
253 fprintf (asm_out_file, "\t! full set regs + partial_save\n");
254 fprintf (asm_out_file, "\tsmw.adm\t$r15, [$sp], $r27, 0x2\n");
255 fprintf (asm_out_file, "\tsmw.adm\t$r0, [$sp], $r5, 0x0\n");
256 }
257 }
258
259 fprintf (asm_out_file, "\tmovi\t$r0, %d ! preparing software vid\n",
260 vector_id - vector_number_offset);
261 fprintf (asm_out_file, "\tj\t%s ! jump to first level handler\n",
262 first_level_handler_name);
263 }
264
265 nds32_emit_section_tail_template (symbol_name);
266 }
267
268 /* Function to emit isr reset handler content.
269 Including all jmptbl/vector references, jmptbl section,
270 vector section, nmi handler section, and warm handler section. */
271 static void
272 nds32_emit_isr_reset_content (void)
273 {
274 unsigned int i;
275 unsigned int total_n_vectors;
276 const char *vs_str;
277 char reset_handler_name[100];
278 char section_name[100];
279 char symbol_name[100];
280
281 total_n_vectors = nds32_isr_vectors[0].total_n_vectors;
282 vs_str = (nds32_isr_vector_size == 4) ? "_4b" : "";
283
284 fprintf (asm_out_file, "\t! RESET HANDLER CONTENT - BEGIN !\n");
285
286 /* Create references in .rodata according to total number of vectors. */
287 fprintf (asm_out_file, "\t.section\t.rodata\n");
288 fprintf (asm_out_file, "\t.align\t2\n");
289
290 /* Emit jmptbl references. */
291 fprintf (asm_out_file, "\t ! references to jmptbl section entries\n");
292 for (i = 0; i < total_n_vectors; i++)
293 fprintf (asm_out_file, "\t.word\t_nds32_jmptbl_%02d\n", i);
294
295 /* Emit vector references. */
296 fprintf (asm_out_file, "\t ! references to vector section entries\n");
297 for (i = 0; i < total_n_vectors; i++)
298 fprintf (asm_out_file, "\t.word\t_nds32_vector_%02d%s\n", i, vs_str);
299
300 /* Emit jmptbl_00 section. */
301 snprintf (section_name, sizeof (section_name), ".nds32_jmptbl.00");
302 snprintf (symbol_name, sizeof (symbol_name), "_nds32_jmptbl_00");
303
304 fprintf (asm_out_file, "\t! ....................................\n");
305 nds32_emit_section_head_template (section_name, symbol_name, 2, true);
306 fprintf (asm_out_file, "\t.word\t%s\n",
307 nds32_isr_vectors[0].func_name);
308 nds32_emit_section_tail_template (symbol_name);
309
310 /* Emit vector_00 section. */
311 snprintf (section_name, sizeof (section_name), ".nds32_vector.00");
312 snprintf (symbol_name, sizeof (symbol_name), "_nds32_vector_00%s", vs_str);
313 snprintf (reset_handler_name, sizeof (reset_handler_name),
314 "_nds32_reset%s", vs_str);
315
316 fprintf (asm_out_file, "\t! ....................................\n");
317 nds32_emit_section_head_template (section_name, symbol_name,
318 floor_log2 (nds32_isr_vector_size), false);
319 fprintf (asm_out_file, "\tj\t%s ! jump to reset handler\n",
320 reset_handler_name);
321 nds32_emit_section_tail_template (symbol_name);
322
323 /* Emit nmi handler section. */
324 snprintf (section_name, sizeof (section_name), ".nds32_nmih");
325 snprintf (symbol_name, sizeof (symbol_name), "_nds32_nmih");
326
327 fprintf (asm_out_file, "\t! ....................................\n");
328 nds32_emit_section_head_template (section_name, symbol_name, 2, true);
329 fprintf (asm_out_file, "\t.word\t%s\n",
330 (strlen (nds32_isr_vectors[0].nmi_name) == 0)
331 ? "0"
332 : nds32_isr_vectors[0].nmi_name);
333 nds32_emit_section_tail_template (symbol_name);
334
335 /* Emit warm handler section. */
336 snprintf (section_name, sizeof (section_name), ".nds32_wrh");
337 snprintf (symbol_name, sizeof (symbol_name), "_nds32_wrh");
338
339 fprintf (asm_out_file, "\t! ....................................\n");
340 nds32_emit_section_head_template (section_name, symbol_name, 2, true);
341 fprintf (asm_out_file, "\t.word\t%s\n",
342 (strlen (nds32_isr_vectors[0].warm_name) == 0)
343 ? "0"
344 : nds32_isr_vectors[0].warm_name);
345 nds32_emit_section_tail_template (symbol_name);
346
347 fprintf (asm_out_file, "\t! RESET HANDLER CONTENT - END !\n");
348 }
349
350 /* Function for nds32_merge_decl_attributes() and nds32_insert_attributes()
351 to check if there are any conflict isr-specific attributes being set.
352 We need to check:
353 1. Only 'save_all' or 'partial_save' in the attributes.
354 2. Only 'nested', 'not_nested', or 'nested_ready' in the attributes.
355 3. Only 'interrupt', 'exception', or 'reset' in the attributes. */
356 void
357 nds32_check_isr_attrs_conflict (tree func_decl, tree func_attrs)
358 {
359 int save_all_p, partial_save_p;
360 int nested_p, not_nested_p, nested_ready_p;
361 int intr_p, excp_p, reset_p;
362
363 /* Initialize variables. */
364 save_all_p = partial_save_p = 0;
365 nested_p = not_nested_p = nested_ready_p = 0;
366 intr_p = excp_p = reset_p = 0;
367
368 /* We must check at MOST one attribute to set save-reg. */
369 if (lookup_attribute ("save_all", func_attrs))
370 save_all_p = 1;
371 if (lookup_attribute ("partial_save", func_attrs))
372 partial_save_p = 1;
373
374 if ((save_all_p + partial_save_p) > 1)
375 error ("multiple save reg attributes to function %qD", func_decl);
376
377 /* We must check at MOST one attribute to set nested-type. */
378 if (lookup_attribute ("nested", func_attrs))
379 nested_p = 1;
380 if (lookup_attribute ("not_nested", func_attrs))
381 not_nested_p = 1;
382 if (lookup_attribute ("nested_ready", func_attrs))
383 nested_ready_p = 1;
384
385 if ((nested_p + not_nested_p + nested_ready_p) > 1)
386 error ("multiple nested types attributes to function %qD", func_decl);
387
388 /* We must check at MOST one attribute to
389 set interrupt/exception/reset. */
390 if (lookup_attribute ("interrupt", func_attrs))
391 intr_p = 1;
392 if (lookup_attribute ("exception", func_attrs))
393 excp_p = 1;
394 if (lookup_attribute ("reset", func_attrs))
395 reset_p = 1;
396
397 if ((intr_p + excp_p + reset_p) > 1)
398 error ("multiple interrupt attributes to function %qD", func_decl);
399 }
400
401 /* Function to construct isr vectors information array.
402 We DO NOT HAVE TO check if the attributes are valid
403 because those works are supposed to be done on
404 nds32_merge_decl_attributes() and nds32_insert_attributes(). */
405 void
406 nds32_construct_isr_vectors_information (tree func_attrs,
407 const char *func_name)
408 {
409 tree save_all, partial_save;
410 tree nested, not_nested, nested_ready;
411 tree intr, excp, reset;
412
413 save_all = lookup_attribute ("save_all", func_attrs);
414 partial_save = lookup_attribute ("partial_save", func_attrs);
415
416 nested = lookup_attribute ("nested", func_attrs);
417 not_nested = lookup_attribute ("not_nested", func_attrs);
418 nested_ready = lookup_attribute ("nested_ready", func_attrs);
419
420 intr = lookup_attribute ("interrupt", func_attrs);
421 excp = lookup_attribute ("exception", func_attrs);
422 reset = lookup_attribute ("reset", func_attrs);
423
424 /* If there is no interrupt/exception/reset, we can return immediately. */
425 if (!intr && !excp && !reset)
426 return;
427
428 /* If we are here, either we have interrupt/exception,
429 or reset attribute. */
430 if (intr || excp)
431 {
432 tree id_list;
433
434 /* Prepare id list so that we can traverse and set vector id. */
435 id_list = (intr) ? (TREE_VALUE (intr)) : (TREE_VALUE (excp));
436
437 while (id_list)
438 {
439 tree id;
440 int vector_id;
441 unsigned int vector_number_offset;
442
443 /* The way to handle interrupt or exception is the same,
444 we just need to take care of actual vector number.
445 For interrupt(0..63), the actual vector number is (9..72).
446 For exception(1..8), the actual vector number is (1..8). */
447 vector_number_offset = (intr) ? (9) : (0);
448
449 /* Pick up each vector id value. */
450 id = TREE_VALUE (id_list);
451 /* Add vector_number_offset to get actual vector number. */
452 vector_id = TREE_INT_CST_LOW (id) + vector_number_offset;
453
454 /* Enable corresponding vector and set function name. */
455 nds32_isr_vectors[vector_id].category = (intr)
456 ? (NDS32_ISR_INTERRUPT)
457 : (NDS32_ISR_EXCEPTION);
458 strcpy (nds32_isr_vectors[vector_id].func_name, func_name);
459
460 /* Set register saving scheme. */
461 if (save_all)
462 nds32_isr_vectors[vector_id].save_reg = NDS32_SAVE_ALL;
463 else if (partial_save)
464 nds32_isr_vectors[vector_id].save_reg = NDS32_PARTIAL_SAVE;
465
466 /* Set nested type. */
467 if (nested)
468 nds32_isr_vectors[vector_id].nested_type = NDS32_NESTED;
469 else if (not_nested)
470 nds32_isr_vectors[vector_id].nested_type = NDS32_NOT_NESTED;
471 else if (nested_ready)
472 nds32_isr_vectors[vector_id].nested_type = NDS32_NESTED_READY;
473
474 /* Advance to next id. */
475 id_list = TREE_CHAIN (id_list);
476 }
477 }
478 else
479 {
480 tree id_list;
481 tree id;
482 tree nmi, warm;
483
484 /* Deal with reset attribute. Its vector number is always 0. */
485 nds32_isr_vectors[0].category = NDS32_ISR_RESET;
486
487 /* Prepare id_list and identify id value so that
488 we can set total number of vectors. */
489 id_list = TREE_VALUE (reset);
490 id = TREE_VALUE (id_list);
491
492 /* The total vectors = interrupt + exception numbers + reset.
493 There are 8 exception and 1 reset in nds32 architecture. */
494 nds32_isr_vectors[0].total_n_vectors = TREE_INT_CST_LOW (id) + 8 + 1;
495 strcpy (nds32_isr_vectors[0].func_name, func_name);
496
497 /* Retrieve nmi and warm function. */
498 nmi = lookup_attribute ("nmi", func_attrs);
499 warm = lookup_attribute ("warm", func_attrs);
500
501 if (nmi != NULL_TREE)
502 {
503 tree nmi_func_list;
504 tree nmi_func;
505
506 nmi_func_list = TREE_VALUE (nmi);
507 nmi_func = TREE_VALUE (nmi_func_list);
508
509 /* Record nmi function name. */
510 strcpy (nds32_isr_vectors[0].nmi_name,
511 IDENTIFIER_POINTER (nmi_func));
512 }
513
514 if (warm != NULL_TREE)
515 {
516 tree warm_func_list;
517 tree warm_func;
518
519 warm_func_list = TREE_VALUE (warm);
520 warm_func = TREE_VALUE (warm_func_list);
521
522 /* Record warm function name. */
523 strcpy (nds32_isr_vectors[0].warm_name,
524 IDENTIFIER_POINTER (warm_func));
525 }
526 }
527 }
528
529 /* A helper function to handle isr stuff at the beginning of asm file. */
530 void
531 nds32_asm_file_start_for_isr (void)
532 {
533 int i;
534
535 /* Initialize isr vector information array before compiling functions. */
536 for (i = 0; i < NDS32_N_ISR_VECTORS; i++)
537 {
538 nds32_isr_vectors[i].category = NDS32_ISR_NONE;
539 strcpy (nds32_isr_vectors[i].func_name, "");
540 nds32_isr_vectors[i].save_reg = NDS32_PARTIAL_SAVE;
541 nds32_isr_vectors[i].nested_type = NDS32_NOT_NESTED;
542 nds32_isr_vectors[i].total_n_vectors = 0;
543 strcpy (nds32_isr_vectors[i].nmi_name, "");
544 strcpy (nds32_isr_vectors[i].warm_name, "");
545 }
546 }
547
548 /* A helper function to handle isr stuff at the end of asm file. */
549 void
550 nds32_asm_file_end_for_isr (void)
551 {
552 int i;
553
554 /* If all the vectors are NDS32_ISR_NONE, we can return immediately. */
555 for (i = 0; i < NDS32_N_ISR_VECTORS; i++)
556 if (nds32_isr_vectors[i].category != NDS32_ISR_NONE)
557 break;
558
559 if (i == NDS32_N_ISR_VECTORS)
560 return;
561
562 /* At least one vector is NOT NDS32_ISR_NONE,
563 we should output isr vector information. */
564 fprintf (asm_out_file, "\t! ------------------------------------\n");
565 fprintf (asm_out_file, "\t! The isr vector information:\n");
566 fprintf (asm_out_file, "\t! ------------------------------------\n");
567
568 /* Check reset handler first. Its vector number is always 0. */
569 if (nds32_isr_vectors[0].category == NDS32_ISR_RESET)
570 {
571 nds32_emit_isr_reset_content ();
572 fprintf (asm_out_file, "\t! ------------------------------------\n");
573 }
574
575 /* Check other vectors, starting from vector number 1. */
576 for (i = 1; i < NDS32_N_ISR_VECTORS; i++)
577 {
578 if (nds32_isr_vectors[i].category == NDS32_ISR_INTERRUPT
579 || nds32_isr_vectors[i].category == NDS32_ISR_EXCEPTION)
580 {
581 /* Found one vector which is interupt or exception.
582 Output its jmptbl and vector section content. */
583 fprintf (asm_out_file, "\t! interrupt/exception vector %02d\n", i);
584 fprintf (asm_out_file, "\t! ------------------------------------\n");
585 nds32_emit_isr_jmptbl_section (i);
586 fprintf (asm_out_file, "\t! ....................................\n");
587 nds32_emit_isr_vector_section (i);
588 fprintf (asm_out_file, "\t! ------------------------------------\n");
589 }
590 }
591 }
592
593 /* Return true if FUNC is a isr function. */
594 bool
595 nds32_isr_function_p (tree func)
596 {
597 tree t_intr;
598 tree t_excp;
599 tree t_reset;
600
601 tree attrs;
602
603 if (TREE_CODE (func) != FUNCTION_DECL)
604 abort ();
605
606 attrs = DECL_ATTRIBUTES (func);
607
608 t_intr = lookup_attribute ("interrupt", attrs);
609 t_excp = lookup_attribute ("exception", attrs);
610 t_reset = lookup_attribute ("reset", attrs);
611
612 return ((t_intr != NULL_TREE)
613 || (t_excp != NULL_TREE)
614 || (t_reset != NULL_TREE));
615 }
616
617 /* ------------------------------------------------------------------------ */