1 /* Subroutines used for calculate rtx costs of Andes NDS32 cpu for GNU compiler 2 Copyright (C) 2012-2019 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 #define IN_TARGET_CODE 1 24 25 #include "config.h" 26 #include "system.h" 27 #include "coretypes.h" 28 #include "backend.h" 29 #include "target.h" 30 #include "rtl.h" 31 #include "tree.h" 32 #include "memmodel.h" 33 #include "tm_p.h" 34 #include "optabs.h" /* For GEN_FCN. */ 35 #include "recog.h" 36 #include "tm-constrs.h" 37 #include "tree-pass.h" 38 39 /* ------------------------------------------------------------------------ */ 40 41 typedef bool (*rtx_cost_func) (rtx, int, int, int, int*); 42 43 struct rtx_cost_model_t { 44 rtx_cost_func speed_prefer; 45 rtx_cost_func size_prefer; 46 }; 47 48 static rtx_cost_model_t rtx_cost_model; 49 50 static int insn_size_16bit; /* Initial at nds32_init_rtx_costs. */ 51 static const int insn_size_32bit = 4; 52 53 static bool 54 nds32_rtx_costs_speed_prefer (rtx x ATTRIBUTE_UNUSED, 55 int code, 56 int outer_code ATTRIBUTE_UNUSED, 57 int opno ATTRIBUTE_UNUSED, 58 int *total) 59 { 60 rtx op0; 61 rtx op1; 62 machine_mode mode = GET_MODE (x); 63 /* Scale cost by mode size. */ 64 int cost = COSTS_N_INSNS (GET_MODE_SIZE (mode) / GET_MODE_SIZE (SImode)); 65 66 switch (code) 67 { 68 case USE: 69 /* Used in combine.c as a marker. */ 70 *total = 0; 71 return true; 72 73 case CONST_INT: 74 /* When not optimizing for size, we care more about the cost 75 of hot code, and hot code is often in a loop. If a constant 76 operand needs to be forced into a register, we will often be 77 able to hoist the constant load out of the loop, so the load 78 should not contribute to the cost. */ 79 if (outer_code == SET || outer_code == PLUS) 80 *total = satisfies_constraint_Is20 (x) ? 0 : 4; 81 else if (outer_code == AND || outer_code == IOR || outer_code == XOR 82 || outer_code == MINUS) 83 *total = satisfies_constraint_Iu15 (x) ? 0 : 4; 84 else if (outer_code == ASHIFT || outer_code == ASHIFTRT 85 || outer_code == LSHIFTRT) 86 *total = satisfies_constraint_Iu05 (x) ? 0 : 4; 87 else if (GET_RTX_CLASS (outer_code) == RTX_COMPARE 88 || GET_RTX_CLASS (outer_code) == RTX_COMM_COMPARE) 89 *total = satisfies_constraint_Is16 (x) ? 0 : 4; 90 else 91 *total = COSTS_N_INSNS (1); 92 return true; 93 94 case CONST: 95 case LO_SUM: 96 case HIGH: 97 case SYMBOL_REF: 98 *total = COSTS_N_INSNS (1); 99 return true; 100 101 case MEM: 102 *total = COSTS_N_INSNS (1); 103 return true; 104 105 case SET: 106 op0 = SET_DEST (x); 107 op1 = SET_SRC (x); 108 mode = GET_MODE (op0); 109 /* Scale cost by mode size. */ 110 cost = COSTS_N_INSNS (GET_MODE_SIZE (mode) / GET_MODE_SIZE (SImode)); 111 112 switch (GET_CODE (op1)) 113 { 114 case REG: 115 case SUBREG: 116 /* Register move and Store instructions. */ 117 if ((REG_P (op0) || MEM_P (op0)) 118 && GET_MODE_SIZE (mode) <= GET_MODE_SIZE (DImode)) 119 *total = COSTS_N_INSNS (1); 120 else 121 *total = cost; 122 return true; 123 124 case MEM: 125 /* Load instructions. */ 126 if (REG_P (op0) && GET_MODE_SIZE (mode) <= GET_MODE_SIZE (DImode)) 127 *total = COSTS_N_INSNS (1); 128 else 129 *total = cost; 130 return true; 131 132 case CONST_INT: 133 /* movi instruction. */ 134 if (REG_P (op0) && GET_MODE_SIZE (mode) < GET_MODE_SIZE (DImode)) 135 { 136 if (satisfies_constraint_Is20 (op1)) 137 *total = COSTS_N_INSNS (1) - 1; 138 else 139 *total = COSTS_N_INSNS (2); 140 } 141 else 142 *total = cost; 143 return true; 144 145 case CONST: 146 case SYMBOL_REF: 147 case LABEL_REF: 148 /* la instruction. */ 149 if (REG_P (op0) && GET_MODE_SIZE (mode) < GET_MODE_SIZE (DImode)) 150 *total = COSTS_N_INSNS (1) - 1; 151 else 152 *total = cost; 153 return true; 154 case VEC_SELECT: 155 *total = cost; 156 return true; 157 158 default: 159 *total = cost; 160 return true; 161 } 162 163 case PLUS: 164 op0 = XEXP (x, 0); 165 op1 = XEXP (x, 1); 166 167 if (GET_MODE_SIZE (mode) >= GET_MODE_SIZE (DImode)) 168 *total = cost; 169 else if (GET_CODE (op0) == MULT || GET_CODE (op0) == LSHIFTRT 170 || GET_CODE (op1) == MULT || GET_CODE (op1) == LSHIFTRT) 171 /* ALU_SHIFT */ 172 *total = COSTS_N_INSNS (2); 173 174 else if ((GET_CODE (op1) == CONST_INT 175 && satisfies_constraint_Is15 (op1)) 176 || REG_P (op1)) 177 /* ADD instructions */ 178 *total = COSTS_N_INSNS (1); 179 else 180 /* ADD instructions: IMM out of range. */ 181 *total = COSTS_N_INSNS (2); 182 return true; 183 184 case MINUS: 185 op0 = XEXP (x, 0); 186 op1 = XEXP (x, 1); 187 188 if (GET_MODE_SIZE (mode) >= GET_MODE_SIZE (DImode)) 189 *total = cost; 190 else if (GET_CODE (op0) == MULT || GET_CODE (op0) == LSHIFTRT 191 || GET_CODE (op1) == MULT || GET_CODE (op1) == LSHIFTRT) 192 /* ALU_SHIFT */ 193 *total = COSTS_N_INSNS (2); 194 else if ((GET_CODE (op0) == CONST_INT 195 && satisfies_constraint_Is15 (op0)) 196 || REG_P (op0)) 197 /* SUB instructions */ 198 *total = COSTS_N_INSNS (1); 199 else 200 /* SUB instructions: IMM out of range. */ 201 *total = COSTS_N_INSNS (2); 202 return true; 203 204 case TRUNCATE: 205 /* TRUNCATE and AND behavior is same. */ 206 *total = COSTS_N_INSNS (1); 207 return true; 208 209 case AND: 210 case IOR: 211 case XOR: 212 op0 = XEXP (x, 0); 213 op1 = XEXP (x, 1); 214 215 if (NDS32_EXT_DSP_P ()) 216 { 217 /* We prefer (and (ior) (ior)) than (ior (and) (and)) for 218 synthetize pk** and insb instruction. */ 219 if (code == AND && GET_CODE (op0) == IOR && GET_CODE (op1) == IOR) 220 return COSTS_N_INSNS (1); 221 222 if (code == IOR && GET_CODE (op0) == AND && GET_CODE (op1) == AND) 223 return COSTS_N_INSNS (10); 224 } 225 226 if (GET_MODE_SIZE (mode) >= GET_MODE_SIZE (DImode)) 227 *total = cost; 228 else if (GET_CODE (op0) == ASHIFT || GET_CODE (op0) == LSHIFTRT) 229 *total = COSTS_N_INSNS (2); 230 else if ((GET_CODE (op1) == CONST_INT 231 && satisfies_constraint_Iu15 (op1)) 232 || REG_P (op1)) 233 /* AND, OR, XOR instructions */ 234 *total = COSTS_N_INSNS (1); 235 else if (code == AND || GET_CODE (op0) == NOT) 236 /* BITC instruction */ 237 *total = COSTS_N_INSNS (1); 238 else 239 /* AND, OR, XOR instructions: IMM out of range. */ 240 *total = COSTS_N_INSNS (2); 241 return true; 242 243 case MULT: 244 if (GET_MODE (x) == DImode 245 || GET_CODE (XEXP (x, 1)) == SIGN_EXTEND 246 || GET_CODE (XEXP (x, 1)) == ZERO_EXTEND) 247 /* MUL instructions */ 248 *total = COSTS_N_INSNS (1); 249 else if (GET_MODE_SIZE (mode) >= GET_MODE_SIZE (DImode)) 250 *total = cost; 251 else if (outer_code == PLUS || outer_code == MINUS) 252 *total = COSTS_N_INSNS (2); 253 else if ((GET_CODE (XEXP (x, 1)) == CONST_INT 254 && satisfies_constraint_Iu05 (XEXP (x, 1))) 255 || REG_P (XEXP (x, 1))) 256 /* MUL instructions */ 257 *total = COSTS_N_INSNS (1); 258 else 259 /* MUL instructions: IMM out of range. */ 260 *total = COSTS_N_INSNS (2); 261 262 if (TARGET_MUL_SLOW) 263 *total += COSTS_N_INSNS (4); 264 265 return true; 266 267 case LSHIFTRT: 268 if (GET_MODE_SIZE (mode) >= GET_MODE_SIZE (DImode)) 269 *total = cost; 270 else if (outer_code == PLUS || outer_code == MINUS 271 || outer_code == AND || outer_code == IOR 272 || outer_code == XOR) 273 *total = COSTS_N_INSNS (2); 274 else if ((GET_CODE (XEXP (x, 1)) == CONST_INT 275 && satisfies_constraint_Iu05 (XEXP (x, 1))) 276 || REG_P (XEXP (x, 1))) 277 /* SRL instructions */ 278 *total = COSTS_N_INSNS (1); 279 else 280 /* SRL instructions: IMM out of range. */ 281 *total = COSTS_N_INSNS (2); 282 return true; 283 284 case ASHIFT: 285 if (GET_MODE_SIZE (mode) >= GET_MODE_SIZE (DImode)) 286 *total = cost; 287 else if (outer_code == AND || outer_code == IOR 288 || outer_code == XOR) 289 *total = COSTS_N_INSNS (2); 290 else if ((GET_CODE (XEXP (x, 1)) == CONST_INT 291 && satisfies_constraint_Iu05 (XEXP (x, 1))) 292 || REG_P (XEXP (x, 1))) 293 /* SLL instructions */ 294 *total = COSTS_N_INSNS (1); 295 else 296 /* SLL instructions: IMM out of range. */ 297 *total = COSTS_N_INSNS (2); 298 return true; 299 300 case ASHIFTRT: 301 case ROTATERT: 302 if (GET_MODE_SIZE (mode) >= GET_MODE_SIZE (DImode)) 303 *total = cost; 304 else if ((GET_CODE (XEXP (x, 1)) == CONST_INT 305 && satisfies_constraint_Iu05 (XEXP (x, 1))) 306 || REG_P (XEXP (x, 1))) 307 /* ROTR, SLL instructions */ 308 *total = COSTS_N_INSNS (1); 309 else 310 /* ROTR, SLL instructions: IMM out of range. */ 311 *total = COSTS_N_INSNS (2); 312 return true; 313 314 case LT: 315 case LTU: 316 if (outer_code == SET) 317 { 318 if ((GET_CODE (XEXP (x, 1)) == CONST_INT 319 && satisfies_constraint_Iu15 (XEXP (x, 1))) 320 || REG_P (XEXP (x, 1))) 321 /* SLT, SLTI instructions */ 322 *total = COSTS_N_INSNS (1); 323 else 324 /* SLT, SLT instructions: IMM out of range. */ 325 *total = COSTS_N_INSNS (2); 326 } 327 else 328 /* branch */ 329 *total = COSTS_N_INSNS (2); 330 return true; 331 332 case EQ: 333 case NE: 334 case GE: 335 case LE: 336 case GT: 337 /* branch */ 338 *total = COSTS_N_INSNS (2); 339 return true; 340 341 case IF_THEN_ELSE: 342 if (GET_CODE (XEXP (x, 1)) == LABEL_REF) 343 /* branch */ 344 *total = COSTS_N_INSNS (2); 345 else 346 /* cmovz, cmovn instructions */ 347 *total = COSTS_N_INSNS (1); 348 return true; 349 350 case LABEL_REF: 351 if (outer_code == IF_THEN_ELSE) 352 /* branch */ 353 *total = COSTS_N_INSNS (2); 354 else 355 *total = COSTS_N_INSNS (1); 356 return true; 357 358 case ZERO_EXTEND: 359 case SIGN_EXTEND: 360 if (MEM_P (XEXP (x, 0))) 361 /* Using memory access. */ 362 *total = COSTS_N_INSNS (1); 363 else 364 /* Zero extend and sign extend instructions. */ 365 *total = COSTS_N_INSNS (1); 366 return true; 367 368 case NEG: 369 case NOT: 370 *total = COSTS_N_INSNS (1); 371 return true; 372 373 case DIV: 374 case UDIV: 375 case MOD: 376 case UMOD: 377 *total = COSTS_N_INSNS (20); 378 return true; 379 380 case CALL: 381 *total = COSTS_N_INSNS (2); 382 return true; 383 384 case CLZ: 385 case SMIN: 386 case SMAX: 387 case ZERO_EXTRACT: 388 if (TARGET_EXT_PERF) 389 *total = COSTS_N_INSNS (1); 390 else 391 *total = COSTS_N_INSNS (3); 392 return true; 393 case VEC_SELECT: 394 *total = COSTS_N_INSNS (1); 395 return true; 396 397 default: 398 *total = COSTS_N_INSNS (3); 399 return true; 400 } 401 } 402 403 static bool 404 nds32_rtx_costs_size_prefer (rtx x, 405 int code, 406 int outer_code, 407 int opno ATTRIBUTE_UNUSED, 408 int *total) 409 { 410 /* In gcc/rtl.h, the default value of COSTS_N_INSNS(N) is N*4. 411 We treat it as 4-byte cost for each instruction 412 under code size consideration. */ 413 switch (code) 414 { 415 case SET: 416 /* For 'SET' rtx, we need to return false 417 so that it can recursively calculate costs. */ 418 return false; 419 420 case USE: 421 /* Used in combine.c as a marker. */ 422 *total = 0; 423 break; 424 425 case CONST_INT: 426 /* All instructions involving constant operation 427 need to be considered for cost evaluation. */ 428 if (outer_code == SET) 429 { 430 /* (set X imm5s), use movi55, 2-byte cost. 431 (set X imm20s), use movi, 4-byte cost. 432 (set X BIG_INT), use sethi/ori, 8-byte cost. */ 433 if (satisfies_constraint_Is05 (x)) 434 *total = insn_size_16bit; 435 else if (satisfies_constraint_Is20 (x)) 436 *total = insn_size_32bit; 437 else 438 *total = insn_size_32bit * 2; 439 } 440 else if (outer_code == PLUS || outer_code == MINUS) 441 { 442 /* Possible addi333/subi333 or subi45/addi45, 2-byte cost. 443 General case, cost 1 instruction with 4-byte. */ 444 if (satisfies_constraint_Iu05 (x)) 445 *total = insn_size_16bit; 446 else 447 *total = insn_size_32bit; 448 } 449 else if (outer_code == ASHIFT) 450 { 451 /* Possible slli333, 2-byte cost. 452 General case, cost 1 instruction with 4-byte. */ 453 if (satisfies_constraint_Iu03 (x)) 454 *total = insn_size_16bit; 455 else 456 *total = insn_size_32bit; 457 } 458 else if (outer_code == ASHIFTRT || outer_code == LSHIFTRT) 459 { 460 /* Possible srai45 or srli45, 2-byte cost. 461 General case, cost 1 instruction with 4-byte. */ 462 if (satisfies_constraint_Iu05 (x)) 463 *total = insn_size_16bit; 464 else 465 *total = insn_size_32bit; 466 } 467 else 468 { 469 /* For other cases, simply set it 4-byte cost. */ 470 *total = insn_size_32bit; 471 } 472 break; 473 474 case CONST_DOUBLE: 475 /* It requires high part and low part processing, set it 8-byte cost. */ 476 *total = insn_size_32bit * 2; 477 break; 478 479 case CONST: 480 case SYMBOL_REF: 481 *total = insn_size_32bit * 2; 482 break; 483 484 default: 485 /* For other cases, generally we set it 4-byte cost 486 and stop resurively traversing. */ 487 *total = insn_size_32bit; 488 break; 489 } 490 491 return true; 492 } 493 494 void 495 nds32_init_rtx_costs (void) 496 { 497 rtx_cost_model.speed_prefer = nds32_rtx_costs_speed_prefer; 498 rtx_cost_model.size_prefer = nds32_rtx_costs_size_prefer; 499 500 if (TARGET_16_BIT) 501 insn_size_16bit = 2; 502 else 503 insn_size_16bit = 4; 504 } 505 506 /* This target hook describes the relative costs of RTL expressions. 507 Return 'true' when all subexpressions of x have been processed. 508 Return 'false' to sum the costs of sub-rtx, plus cost of this operation. 509 Refer to gcc/rtlanal.c for more information. */ 510 bool 511 nds32_rtx_costs_impl (rtx x, 512 machine_mode mode ATTRIBUTE_UNUSED, 513 int outer_code, 514 int opno, 515 int *total, 516 bool speed) 517 { 518 int code = GET_CODE (x); 519 520 /* According to 'speed', use suitable cost model section. */ 521 if (speed) 522 return rtx_cost_model.speed_prefer(x, code, outer_code, opno, total); 523 else 524 return rtx_cost_model.size_prefer(x, code, outer_code, opno, total); 525 } 526 527 528 int nds32_address_cost_speed_prefer (rtx address) 529 { 530 rtx plus0, plus1; 531 enum rtx_code code; 532 533 code = GET_CODE (address); 534 535 switch (code) 536 { 537 case POST_MODIFY: 538 case POST_INC: 539 case POST_DEC: 540 /* We encourage that rtx contains 541 POST_MODIFY/POST_INC/POST_DEC behavior. */ 542 return COSTS_N_INSNS (1) - 2; 543 544 case SYMBOL_REF: 545 /* We can have gp-relative load/store for symbol_ref. 546 Have it 4-byte cost. */ 547 return COSTS_N_INSNS (2); 548 549 case CONST: 550 /* It is supposed to be the pattern (const (plus symbol_ref const_int)). 551 Have it 4-byte cost. */ 552 return COSTS_N_INSNS (2); 553 554 case REG: 555 /* Simply return 4-byte costs. */ 556 return COSTS_N_INSNS (1) - 2; 557 558 case PLUS: 559 /* We do not need to check if the address is a legitimate address, 560 because this hook is never called with an invalid address. 561 But we better check the range of 562 const_int value for cost, if it exists. */ 563 plus0 = XEXP (address, 0); 564 plus1 = XEXP (address, 1); 565 566 if (REG_P (plus0) && CONST_INT_P (plus1)) 567 return COSTS_N_INSNS (1) - 2; 568 else if (ARITHMETIC_P (plus0) || ARITHMETIC_P (plus1)) 569 return COSTS_N_INSNS (1) - 1; 570 else if (REG_P (plus0) && REG_P (plus1)) 571 return COSTS_N_INSNS (1); 572 573 /* For other 'plus' situation, make it cost 4-byte. */ 574 return COSTS_N_INSNS (1); 575 576 default: 577 break; 578 } 579 580 return COSTS_N_INSNS (4); 581 582 } 583 584 int nds32_address_cost_speed_fwprop (rtx address) 585 { 586 rtx plus0, plus1; 587 enum rtx_code code; 588 589 code = GET_CODE (address); 590 591 switch (code) 592 { 593 case POST_MODIFY: 594 case POST_INC: 595 case POST_DEC: 596 /* We encourage that rtx contains 597 POST_MODIFY/POST_INC/POST_DEC behavior. */ 598 return 0; 599 600 case SYMBOL_REF: 601 /* We can have gp-relative load/store for symbol_ref. 602 Have it 4-byte cost. */ 603 return COSTS_N_INSNS (2); 604 605 case CONST: 606 /* It is supposed to be the pattern (const (plus symbol_ref const_int)). 607 Have it 4-byte cost. */ 608 return COSTS_N_INSNS (2); 609 610 case REG: 611 /* Simply return 4-byte costs. */ 612 return COSTS_N_INSNS (1); 613 614 case PLUS: 615 /* We do not need to check if the address is a legitimate address, 616 because this hook is never called with an invalid address. 617 But we better check the range of 618 const_int value for cost, if it exists. */ 619 plus0 = XEXP (address, 0); 620 plus1 = XEXP (address, 1); 621 622 if (REG_P (plus0) && CONST_INT_P (plus1)) 623 { 624 /* If it is possible to be lwi333/swi333 form, 625 make it 2-byte cost. */ 626 if (satisfies_constraint_Iu03 (plus1)) 627 return (COSTS_N_INSNS (1) - 2); 628 else 629 return COSTS_N_INSNS (1); 630 } 631 if (ARITHMETIC_P (plus0) || ARITHMETIC_P (plus1)) 632 return COSTS_N_INSNS (1) - 2; 633 else if (REG_P (plus0) && REG_P (plus1)) 634 return COSTS_N_INSNS (1); 635 636 /* For other 'plus' situation, make it cost 4-byte. */ 637 return COSTS_N_INSNS (1); 638 639 default: 640 break; 641 } 642 643 return COSTS_N_INSNS (4); 644 } 645 646 647 int nds32_address_cost_size_prefer (rtx address) 648 { 649 rtx plus0, plus1; 650 enum rtx_code code; 651 652 code = GET_CODE (address); 653 654 switch (code) 655 { 656 case POST_MODIFY: 657 case POST_INC: 658 case POST_DEC: 659 /* We encourage that rtx contains 660 POST_MODIFY/POST_INC/POST_DEC behavior. */ 661 return 0; 662 663 case SYMBOL_REF: 664 /* We can have gp-relative load/store for symbol_ref. 665 Have it 4-byte cost. */ 666 return COSTS_N_INSNS (2); 667 668 case CONST: 669 /* It is supposed to be the pattern (const (plus symbol_ref const_int)). 670 Have it 4-byte cost. */ 671 return COSTS_N_INSNS (2); 672 673 case REG: 674 /* Simply return 4-byte costs. */ 675 return COSTS_N_INSNS (1) - 1; 676 677 case PLUS: 678 /* We do not need to check if the address is a legitimate address, 679 because this hook is never called with an invalid address. 680 But we better check the range of 681 const_int value for cost, if it exists. */ 682 plus0 = XEXP (address, 0); 683 plus1 = XEXP (address, 1); 684 685 if (REG_P (plus0) && CONST_INT_P (plus1)) 686 { 687 /* If it is possible to be lwi333/swi333 form, 688 make it 2-byte cost. */ 689 if (satisfies_constraint_Iu03 (plus1)) 690 return (COSTS_N_INSNS (1) - 2); 691 else 692 return COSTS_N_INSNS (1) - 1; 693 } 694 695 /* (plus (reg) (mult (reg) (const))) */ 696 if (ARITHMETIC_P (plus0) || ARITHMETIC_P (plus1)) 697 return (COSTS_N_INSNS (1) - 1); 698 699 /* For other 'plus' situation, make it cost 4-byte. */ 700 return COSTS_N_INSNS (1); 701 702 default: 703 break; 704 } 705 706 return COSTS_N_INSNS (4); 707 708 } 709 710 int nds32_address_cost_impl (rtx address, 711 machine_mode mode ATTRIBUTE_UNUSED, 712 addr_space_t as ATTRIBUTE_UNUSED, 713 bool speed_p) 714 { 715 if (speed_p) 716 { 717 if (current_pass->tv_id == TV_FWPROP) 718 return nds32_address_cost_speed_fwprop (address); 719 else 720 return nds32_address_cost_speed_prefer (address); 721 } 722 else 723 return nds32_address_cost_size_prefer (address); 724 } 725 726 /* ------------------------------------------------------------------------ */ 727