1 /* 2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 #include "precompiled.hpp" 26 #include "compiler/compileBroker.hpp" 27 #include "compiler/directivesParser.hpp" 28 #include "memory/allocation.inline.hpp" 29 #include "memory/resourceArea.hpp" 30 #include "runtime/os.hpp" 31 #include <string.h> 32 33 void DirectivesParser::push_tmp(CompilerDirectives* dir) { 34 _tmp_depth++; 35 dir->set_next(_tmp_top); 36 _tmp_top = dir; 37 } 38 39 CompilerDirectives* DirectivesParser::pop_tmp() { 40 if (_tmp_top == NULL) { 41 return NULL; 42 } 43 CompilerDirectives* tmp = _tmp_top; 44 _tmp_top = _tmp_top->next(); 45 tmp->set_next(NULL); 46 _tmp_depth--; 47 return tmp; 48 } 49 50 void DirectivesParser::clean_tmp() { 51 CompilerDirectives* tmp = pop_tmp(); 52 while (tmp != NULL) { 53 delete tmp; 54 tmp = pop_tmp(); 55 } 56 assert(_tmp_depth == 0, "Consistency"); 57 } 58 59 int DirectivesParser::parse_string(const char* text, outputStream* st) { 60 DirectivesParser cd(text, st, false); 61 if (cd.valid()) { 62 return cd.install_directives(); 63 } else { 64 cd.clean_tmp(); 65 st->flush(); 66 st->print_cr("Parsing of compiler directives failed"); 67 return -1; 68 } 69 } 70 71 bool DirectivesParser::has_file() { 72 return CompilerDirectivesFile != NULL; 73 } 74 75 bool DirectivesParser::parse_from_flag() { 76 return parse_from_file(CompilerDirectivesFile, tty); 77 } 78 79 bool DirectivesParser::parse_from_file(const char* filename, outputStream* st) { 80 assert(filename != NULL, "Test before calling this"); 81 if (!parse_from_file_inner(filename, st)) { 82 st->print_cr("Could not load file: %s", filename); 83 return false; 84 } 85 return true; 86 } 87 88 bool DirectivesParser::parse_from_file_inner(const char* filename, outputStream* stream) { 89 struct stat st; 90 ResourceMark rm; 91 if (os::stat(filename, &st) == 0) { 92 // found file, open it 93 int file_handle = os::open(filename, 0, 0); 94 if (file_handle != -1) { 95 // read contents into resource array 96 char* buffer = NEW_RESOURCE_ARRAY(char, st.st_size+1); 97 size_t num_read = os::read(file_handle, (char*) buffer, st.st_size); 98 buffer[num_read] = '\0'; 99 // close file 100 os::close(file_handle); 101 return parse_string(buffer, stream) > 0; 102 } 103 } 104 return false; 105 } 106 107 int DirectivesParser::install_directives() { 108 // Check limit 109 if (!DirectivesStack::check_capacity(_tmp_depth, _st)) { 110 clean_tmp(); 111 return 0; 112 } 113 114 // Pop from internal temporary stack and push to compileBroker. 115 CompilerDirectives* tmp = pop_tmp(); 116 int i = 0; 117 while (tmp != NULL) { 118 i++; 119 DirectivesStack::push(tmp); 120 tmp = pop_tmp(); 121 } 122 if (i == 0) { 123 _st->print_cr("No directives in file"); 124 return 0; 125 } else { 126 _st->print_cr("%i compiler directives added", i); 127 if (CompilerDirectivesPrint) { 128 // Print entire directives stack after new has been pushed. 129 DirectivesStack::print(_st); 130 } 131 return i; 132 } 133 } 134 135 DirectivesParser::DirectivesParser(const char* text, outputStream* st, bool silent) 136 : JSON(text, silent, st), depth(0), current_directive(NULL), current_directiveset(NULL), _tmp_top(NULL), _tmp_depth(0) { 137 #ifndef PRODUCT 138 memset(stack, 0, MAX_DEPTH * sizeof(stack[0])); 139 #endif 140 parse(); 141 } 142 143 DirectivesParser::~DirectivesParser() { 144 assert(_tmp_top == NULL, "Consistency"); 145 assert(_tmp_depth == 0, "Consistency"); 146 } 147 148 const DirectivesParser::key DirectivesParser::keys[] = { 149 // name, keytype, allow_array, allowed_mask, set_function 150 { "c1", type_c1, 0, mask(type_directives), NULL, UnknownFlagType }, 151 { "c2", type_c2, 0, mask(type_directives), NULL, UnknownFlagType }, 152 { "match", type_match, 1, mask(type_directives), NULL, UnknownFlagType }, 153 { "inline", type_inline, 1, mask(type_directives) | mask(type_c1) | mask(type_c2), NULL, UnknownFlagType }, 154 155 // Global flags 156 #define common_flag_key(name, type, dvalue, compiler) \ 157 { #name, type_flag, 0, mask(type_directives) | mask(type_c1) | mask(type_c2), &DirectiveSet::set_##name, type##Flag}, 158 compilerdirectives_common_flags(common_flag_key) 159 compilerdirectives_c2_flags(common_flag_key) 160 compilerdirectives_c1_flags(common_flag_key) 161 #undef common_flag_key 162 }; 163 164 const DirectivesParser::key DirectivesParser::dir_array_key = { 165 "top level directives array", type_dir_array, 0, 1 // Lowest bit means allow at top level 166 }; 167 const DirectivesParser::key DirectivesParser::dir_key = { 168 "top level directive", type_directives, 0, mask(type_dir_array) | 1 // Lowest bit means allow at top level 169 }; 170 const DirectivesParser::key DirectivesParser::value_array_key = { 171 "value array", type_value_array, 0, UINT_MAX // Allow all, checked by allow_array on other keys, not by allowed_mask from this key 172 }; 173 174 const DirectivesParser::key* DirectivesParser::lookup_key(const char* str, size_t len) { 175 for (size_t i = 0; i < (sizeof(keys) / sizeof(keys[0])); i++) { 176 if (strncasecmp(keys[i].name, str, len) == 0) { 177 return &keys[i]; 178 } 179 } 180 return NULL; 181 } 182 183 uint DirectivesParser::mask(keytype kt) { 184 return 1 << (kt + 1); 185 } 186 187 bool DirectivesParser::push_key(const char* str, size_t len) { 188 bool result = true; 189 const key* k = lookup_key(str, len); 190 191 if (k == NULL) { 192 // os::strdup 193 char* s = NEW_C_HEAP_ARRAY(char, len + 1, mtCompiler); 194 strncpy(s, str, len); 195 s[len] = '\0'; 196 error(KEY_ERROR, "No such key: '%s'.", s); 197 FREE_C_HEAP_ARRAY(char, s); 198 return false; 199 } 200 201 return push_key(k); 202 } 203 204 bool DirectivesParser::push_key(const key* k) { 205 assert(k->allowedmask != 0, "not allowed anywhere?"); 206 207 // Exceeding the stack should not be possible with a valid compiler directive, 208 // and an invalid should abort before this happens 209 assert(depth < MAX_DEPTH, "exceeded stack depth"); 210 if (depth >= MAX_DEPTH) { 211 error(INTERNAL_ERROR, "Stack depth exceeded."); 212 return false; 213 } 214 215 assert(stack[depth] == NULL, "element not nulled, something is wrong"); 216 217 if (depth == 0 && !(k->allowedmask & 1)) { 218 error(KEY_ERROR, "Key '%s' not allowed at top level.", k->name); 219 return false; 220 } 221 222 if (depth > 0) { 223 const key* prev = stack[depth - 1]; 224 if (!(k->allowedmask & mask(prev->type))) { 225 error(KEY_ERROR, "Key '%s' not allowed after '%s' key.", k->name, prev->name); 226 return false; 227 } 228 } 229 230 stack[depth] = k; 231 depth++; 232 return true; 233 } 234 235 const DirectivesParser::key* DirectivesParser::current_key() { 236 assert(depth > 0, "getting key from empty stack"); 237 if (depth == 0) { 238 return NULL; 239 } 240 return stack[depth - 1]; 241 } 242 243 const DirectivesParser::key* DirectivesParser::pop_key() { 244 assert(depth > 0, "popping empty stack"); 245 if (depth == 0) { 246 error(INTERNAL_ERROR, "Popping empty stack."); 247 return NULL; 248 } 249 depth--; 250 251 const key* k = stack[depth]; 252 #ifndef PRODUCT 253 stack[depth] = NULL; 254 #endif 255 256 return k; 257 } 258 259 bool DirectivesParser::set_option_flag(JSON_TYPE t, JSON_VAL* v, const key* option_key, DirectiveSet* set) { 260 261 void (DirectiveSet::*test)(void *args); 262 test = option_key->set; 263 264 switch (t) { 265 case JSON_TRUE: 266 if (option_key->flag_type != boolFlag) { 267 error(VALUE_ERROR, "Cannot use bool value for an %s flag", flag_type_names[option_key->flag_type]); 268 return false; 269 } else { 270 bool val = true; 271 (set->*test)((void *)&val); 272 } 273 break; 274 275 case JSON_FALSE: 276 if (option_key->flag_type != boolFlag) { 277 error(VALUE_ERROR, "Cannot use bool value for an %s flag", flag_type_names[option_key->flag_type]); 278 return false; 279 } else { 280 bool val = false; 281 (set->*test)((void *)&val); 282 } 283 break; 284 285 case JSON_NUMBER_INT: 286 if (option_key->flag_type == intxFlag) { 287 intx ival = v->int_value; 288 (set->*test)((void *)&ival); 289 } else if (option_key->flag_type == uintxFlag) { 290 uintx ival = v->uint_value; 291 (set->*test)((void *)&ival); 292 } else if (option_key->flag_type == doubleFlag) { 293 double dval = (double)v->int_value; 294 (set->*test)((void *)&dval); 295 } else { 296 error(VALUE_ERROR, "Cannot use int value for an %s flag", flag_type_names[option_key->flag_type]); 297 return false; 298 } 299 break; 300 301 case JSON_NUMBER_FLOAT: 302 if (option_key->flag_type != doubleFlag) { 303 error(VALUE_ERROR, "Cannot use double value for an %s flag", flag_type_names[option_key->flag_type]); 304 return false; 305 } else { 306 double dval = v->double_value; 307 (set->*test)((void *)&dval); 308 } 309 break; 310 311 case JSON_STRING: 312 if (option_key->flag_type != ccstrFlag && option_key->flag_type != ccstrlistFlag) { 313 error(VALUE_ERROR, "Cannot use string value for a %s flag", flag_type_names[option_key->flag_type]); 314 return false; 315 } else { 316 char* s = NEW_C_HEAP_ARRAY(char, v->str.length+1, mtCompiler); 317 strncpy(s, v->str.start, v->str.length + 1); 318 s[v->str.length] = '\0'; 319 (set->*test)((void *)&s); 320 } 321 break; 322 323 default: 324 assert(0, "Should not reach here."); 325 } 326 return true; 327 } 328 329 bool DirectivesParser::set_option(JSON_TYPE t, JSON_VAL* v) { 330 331 const key* option_key = pop_key(); 332 const key* enclosing_key = current_key(); 333 334 if (option_key->type == value_array_key.type) { 335 // Multi value array, we are really setting the value 336 // for the key one step further up. 337 option_key = pop_key(); 338 enclosing_key = current_key(); 339 340 // Repush option_key and multi value marker, since 341 // we need to keep them until all multi values are set. 342 push_key(option_key); 343 push_key(&value_array_key); 344 } 345 346 switch (option_key->type) { 347 case type_flag: 348 { 349 if (current_directiveset == NULL) { 350 assert(depth == 2, "Must not have active directive set"); 351 352 if (!set_option_flag(t, v, option_key, current_directive->_c1_store)) { 353 return false; 354 } 355 if (!set_option_flag(t, v, option_key, current_directive->_c2_store)) { 356 return false; 357 } 358 } else { 359 assert(depth > 2, "Must have active current directive set"); 360 if (!set_option_flag(t, v, option_key, current_directiveset)) { 361 return false; 362 } 363 } 364 break; 365 } 366 367 case type_match: 368 if (t != JSON_STRING) { 369 error(VALUE_ERROR, "Key of type %s needs a value of type string", option_key->name); 370 return false; 371 } 372 if (enclosing_key->type != type_directives) { 373 error(SYNTAX_ERROR, "Match keyword can only exist inside a directive"); 374 return false; 375 } 376 { 377 char* s = NEW_C_HEAP_ARRAY(char, v->str.length + 1, mtCompiler); 378 strncpy(s, v->str.start, v->str.length); 379 s[v->str.length] = '\0'; 380 381 const char* error_msg = NULL; 382 if (!current_directive->add_match(s, error_msg)) { 383 assert (error_msg != NULL, "Must have valid error message"); 384 error(VALUE_ERROR, "Method pattern error: %s", error_msg); 385 } 386 FREE_C_HEAP_ARRAY(char, s); 387 } 388 break; 389 390 case type_inline: 391 if (t != JSON_STRING) { 392 error(VALUE_ERROR, "Key of type %s needs a value of type string", option_key->name); 393 return false; 394 } 395 { 396 //char* s = strndup(v->str.start, v->str.length); 397 char* s = NEW_C_HEAP_ARRAY(char, v->str.length + 1, mtCompiler); 398 strncpy(s, v->str.start, v->str.length); 399 s[v->str.length] = '\0'; 400 401 const char* error_msg = NULL; 402 if (current_directiveset == NULL) { 403 if (current_directive->_c1_store->parse_and_add_inline(s, error_msg)) { 404 if (!current_directive->_c2_store->parse_and_add_inline(s, error_msg)) { 405 assert (error_msg != NULL, "Must have valid error message"); 406 error(VALUE_ERROR, "Method pattern error: %s", error_msg); 407 } 408 } else { 409 assert (error_msg != NULL, "Must have valid error message"); 410 error(VALUE_ERROR, "Method pattern error: %s", error_msg); 411 } 412 } else { 413 if (!current_directiveset->parse_and_add_inline(s, error_msg)) { 414 assert (error_msg != NULL, "Must have valid error message"); 415 error(VALUE_ERROR, "Method pattern error: %s", error_msg); 416 } 417 } 418 FREE_C_HEAP_ARRAY(char, s); 419 } 420 break; 421 422 case type_c1: 423 current_directiveset = current_directive->_c1_store; 424 if (t != JSON_TRUE && t != JSON_FALSE) { 425 error(VALUE_ERROR, "Key of type %s needs a true or false value", option_key->name); 426 return false; 427 } 428 break; 429 430 case type_c2: 431 current_directiveset = current_directive->_c2_store; 432 if (t != JSON_TRUE && t != JSON_FALSE) { 433 error(VALUE_ERROR, "Key of type %s needs a true or false value", option_key->name); 434 return false; 435 } 436 break; 437 438 default: 439 break; 440 } 441 442 return true; 443 } 444 445 bool DirectivesParser::callback(JSON_TYPE t, JSON_VAL* v, uint rlimit) { 446 const key* k; 447 448 if (depth == 0) { 449 switch (t) { 450 case JSON_ARRAY_BEGIN: 451 return push_key(&dir_array_key); 452 453 case JSON_OBJECT_BEGIN: 454 // push synthetic dir_array 455 push_key(&dir_array_key); 456 assert(depth == 1, "Make sure the stack are aligned with the directives"); 457 break; 458 459 default: 460 error(SYNTAX_ERROR, "DirectivesParser can only start with an array containing directive objects, or one single directive."); 461 return false; 462 } 463 } 464 if (depth == 1) { 465 switch (t) { 466 case JSON_OBJECT_BEGIN: 467 // Parsing a new directive. 468 current_directive = new CompilerDirectives(); 469 return push_key(&dir_key); 470 471 case JSON_ARRAY_END: 472 k = pop_key(); 473 474 if (k->type != type_dir_array) { 475 error(SYNTAX_ERROR, "Expected end of directives array"); 476 return false; 477 } 478 return true; 479 480 default: 481 error(SYNTAX_ERROR, "DirectivesParser can only start with an array containing directive objects, or one single directive."); 482 return false; 483 } 484 } else { 485 switch (t) { 486 case JSON_OBJECT_BEGIN: 487 k = current_key(); 488 switch (k->type) { 489 case type_c1: 490 current_directiveset = current_directive->_c1_store; 491 return true; 492 case type_c2: 493 current_directiveset = current_directive->_c2_store; 494 return true; 495 496 case type_dir_array: 497 return push_key(&dir_key); 498 499 default: 500 error(SYNTAX_ERROR, "The key '%s' does not allow an object to follow.", k->name); 501 return false; 502 } 503 return false; 504 505 case JSON_OBJECT_END: 506 k = pop_key(); 507 switch (k->type) { 508 case type_c1: 509 case type_c2: 510 // This is how we now if options apply to a single or both directive sets 511 current_directiveset = NULL; 512 break; 513 514 case type_directives: 515 // Check, finish and push to stack! 516 if (current_directive->match() == NULL) { 517 error(INTERNAL_ERROR, "Directive missing required match."); 518 return false; 519 } 520 current_directive->finalize(_st); 521 push_tmp(current_directive); 522 current_directive = NULL; 523 break; 524 525 default: 526 error(INTERNAL_ERROR, "Object end with wrong key type on stack: %s.", k->name); 527 ShouldNotReachHere(); 528 return false; 529 } 530 return true; 531 532 case JSON_ARRAY_BEGIN: 533 k = current_key(); 534 if (!(k->allow_array_value)) { 535 if (k->type == type_dir_array) { 536 error(SYNTAX_ERROR, "Array not allowed inside top level array, expected directive object."); 537 } else { 538 error(VALUE_ERROR, "The key '%s' does not allow an array of values.", k->name); 539 } 540 return false; 541 } 542 return push_key(&value_array_key); 543 544 case JSON_ARRAY_END: 545 k = pop_key(); // Pop multi value marker 546 assert(k->type == value_array_key.type, "array end for level != 0 should terminate multi value"); 547 k = pop_key(); // Pop key for option that was set 548 return true; 549 550 case JSON_KEY: 551 return push_key(v->str.start, v->str.length); 552 553 case JSON_STRING: 554 case JSON_NUMBER_INT: 555 case JSON_NUMBER_FLOAT: 556 case JSON_TRUE: 557 case JSON_FALSE: 558 case JSON_NULL: 559 return set_option(t, v); 560 561 default: 562 error(INTERNAL_ERROR, "Unknown JSON type: %d.", t); 563 ShouldNotReachHere(); 564 return false; 565 } 566 } 567 } 568 569 #ifndef PRODUCT 570 void DirectivesParser::test(const char* text, bool should_pass) { 571 DirectivesParser cd(text, tty, !VerboseInternalVMTests); 572 if (should_pass) { 573 assert(cd.valid() == true, "failed on a valid DirectivesParser string"); 574 if (VerboseInternalVMTests) { 575 tty->print("-- DirectivesParser test passed as expected --\n"); 576 } 577 } else { 578 assert(cd.valid() == false, "succeeded on an invalid DirectivesParser string"); 579 if (VerboseInternalVMTests) { 580 tty->print("-- DirectivesParser test failed as expected --\n"); 581 } 582 } 583 cd.clean_tmp(); 584 } 585 586 void DirectivesParser::test() { 587 DirectivesParser::test("{}", false); 588 DirectivesParser::test("[]", true); 589 DirectivesParser::test("[{}]", false); 590 DirectivesParser::test("[{},{}]", false); 591 DirectivesParser::test("{},{}", false); 592 593 DirectivesParser::test( 594 "[" "\n" 595 " {" "\n" 596 " match: \"foo/bar.*\"," "\n" 597 " inline : \"+java/util.*\"," "\n" 598 " PrintAssembly: true," "\n" 599 " BreakAtExecute: true," "\n" 600 " }" "\n" 601 "]" "\n", true); 602 603 DirectivesParser::test( 604 "[" "\n" 605 " [" "\n" 606 " {" "\n" 607 " match: \"foo/bar.*\"," "\n" 608 " inline : \"+java/util.*\"," "\n" 609 " PrintAssembly: true," "\n" 610 " BreakAtExecute: true," "\n" 611 " }" "\n" 612 " ]" "\n" 613 "]" "\n", false); 614 615 /*DirectivesParser::test( 616 "[" "\n" 617 " {" "\n" 618 " match: \"foo/bar.*\"," "\n" 619 " c1: {" 620 " PrintIntrinsics: false," "\n" 621 " }" "\n" 622 " }" "\n" 623 "]" "\n", false);*/ 624 625 DirectivesParser::test( 626 "[" "\n" 627 " {" "\n" 628 " match: \"foo/bar.*\"," "\n" 629 " c2: {" "\n" 630 " PrintInlining: false," "\n" 631 " VectorizeDebug: 1," "\n" 632 " VectorizeDebug: -1," "\n" 633 " }" "\n" 634 " }" "\n" 635 "]" "\n", true); 636 637 DirectivesParser::test( 638 "[" "\n" 639 " {" "\n" 640 " match: \"foo/bar.*\"," "\n" 641 " PrintInlining: [" "\n" 642 " true," "\n" 643 " false" "\n" 644 " ]," "\n" 645 " }" "\n" 646 "]" "\n", false); 647 648 DirectivesParser::test( 649 "[" "\n" 650 " {" 651 " // pattern to match against class+method+signature" "\n" 652 " // leading and trailing wildcard (*) allowed" "\n" 653 " match: \"foo/bar.*\"," "\n" 654 "" "\n" 655 " // override defaults for specified compiler" "\n" 656 " // we may differentiate between levels too. TBD." "\n" 657 " c1: {" "\n" 658 " //override c1 presets " "\n" 659 " DumpReplay: false," "\n" 660 " BreakAtCompile: true," "\n" 661 " }," "\n" 662 "" "\n" 663 " c2: {" "\n" 664 " // control inlining of method" "\n" 665 " // + force inline, - dont inline" "\n" 666 " inline : \"+java/util.*\"," "\n" 667 " PrintInlining: true," "\n" 668 " }," "\n" 669 "" "\n" 670 " // directives outside a specific preset applies to all compilers" "\n" 671 " inline : [ \"+java/util.*\", \"-com/sun.*\"]," "\n" 672 " BreakAtExecute: true," "\n" 673 " Log: true," "\n" 674 " }," "\n" 675 " {" "\n" 676 " // matching several patterns require an array" "\n" 677 " match: [\"baz.*\",\"frob.*\"]," "\n" 678 "" "\n" 679 " // applies to all compilers" "\n" 680 " // + force inline, - dont inline" "\n" 681 " inline : [ \"+java/util.*\", \"-com/sun.*\" ]," "\n" 682 " PrintInlining: true," "\n" 683 "" "\n" 684 " // force matching compiles to be blocking/syncronous" "\n" 685 " PrintNMethods: true" "\n" 686 " }," "\n" 687 "]" "\n", true); 688 689 // Test max stack depth 690 DirectivesParser::test( 691 "[" "\n" // depth 1: type_dir_array 692 " {" "\n" // depth 2: type_directives 693 " match: \"*.*\"," // match required 694 " c1:" "\n" // depth 3: type_c1 695 " {" "\n" 696 " inline:" "\n" // depth 4: type_inline 697 " [" "\n" // depth 5: type_value_array 698 " \"foo\"," "\n" 699 " \"bar\"," "\n" 700 " ]" "\n" // depth 3: pop type_value_array and type_inline keys 701 " }" "\n" // depth 2: pop type_c1 key 702 " }" "\n" // depth 1: pop type_directives key 703 "]" "\n", true); // depth 0: pop type_dir_array key 704 705 // Test max stack depth 706 DirectivesParser::test( 707 "[{c1:{c1:{c1:{c1:{c1:{c1:{c1:{}}}}}}}}]", false); 708 709 } 710 711 void DirectivesParser_test() { 712 DirectivesParser::test(); 713 } 714 715 #endif