| /* | |
| * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| * | |
| */ | |
| #include "compiler/compileBroker.hpp" | |
| #include "compiler/directivesParser.hpp" | |
| #include "memory/allocation.inline.hpp" | |
| #include "memory/resourceArea.hpp" | |
| #include "opto/phasetype.hpp" | |
| #include "opto/traceAutoVectorizationTag.hpp" | |
| #include "opto/traceMergeStoresTag.hpp" | |
| #include "runtime/os.hpp" | |
| #include <string.h> | |
| void DirectivesParser::push_tmp(CompilerDirectives* dir) { | |
| _tmp_depth++; | |
| dir->set_next(_tmp_top); | |
| _tmp_top = dir; | |
| } | |
| CompilerDirectives* DirectivesParser::pop_tmp() { | |
| if (_tmp_top == nullptr) { | |
| return nullptr; | |
| } | |
| CompilerDirectives* tmp = _tmp_top; | |
| _tmp_top = _tmp_top->next(); | |
| tmp->set_next(nullptr); | |
| _tmp_depth--; | |
| return tmp; | |
| } | |
| void DirectivesParser::clean_tmp() { | |
| CompilerDirectives* tmp = pop_tmp(); | |
| while (tmp != nullptr) { | |
| delete tmp; | |
| tmp = pop_tmp(); | |
| } | |
| assert(_tmp_depth == 0, "Consistency"); | |
| } | |
| int DirectivesParser::parse_string(const char* text, outputStream* st, bool silent) { | |
| DirectivesParser cd(text, st, silent); | |
| if (cd.valid()) { | |
| return cd.install_directives(); | |
| } else { | |
| cd.clean_tmp(); | |
| st->flush(); | |
| st->print_cr("Parsing of compiler directives failed"); | |
| return -1; | |
| } | |
| } | |
| bool DirectivesParser::has_file() { | |
| return CompilerDirectivesFile != nullptr; | |
| } | |
| bool DirectivesParser::parse_from_flag() { | |
| return parse_from_file(CompilerDirectivesFile, tty); | |
| } | |
| bool DirectivesParser::parse_from_file(const char* filename, outputStream* st, bool silent) { | |
| assert(filename != nullptr, "Test before calling this"); | |
| if (!parse_from_file_inner(filename, st, silent)) { | |
| st->print_cr("Could not load file: %s", filename); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool DirectivesParser::parse_from_file_inner(const char* filename, outputStream* stream, bool silent) { | |
| struct stat st; | |
| ResourceMark rm; | |
| if (os::stat(filename, &st) == 0) { | |
| // found file, open it | |
| int file_handle = os::open(filename, 0, 0); | |
| if (file_handle != -1) { | |
| // read contents into resource array | |
| char* buffer = NEW_RESOURCE_ARRAY(char, st.st_size+1); | |
| ssize_t num_read = ::read(file_handle, (char*) buffer, st.st_size); | |
| ::close(file_handle); | |
| if (num_read >= 0) { | |
| buffer[num_read] = '\0'; | |
| return parse_string(buffer, stream, silent) > 0; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| int DirectivesParser::install_directives() { | |
| // Check limit | |
| if (!DirectivesStack::check_capacity(_tmp_depth, _st)) { | |
| clean_tmp(); | |
| return 0; | |
| } | |
| // Pop from internal temporary stack and push to compileBroker. | |
| CompilerDirectives* tmp = pop_tmp(); | |
| int i = 0; | |
| while (tmp != nullptr) { | |
| i++; | |
| DirectivesStack::push(tmp); | |
| tmp = pop_tmp(); | |
| } | |
| if (i == 0) { | |
| _st->print_cr("No directives in file"); | |
| return 0; | |
| } else { | |
| _st->print_cr("%i compiler directives added", i); | |
| if (CompilerDirectivesPrint) { | |
| // Print entire directives stack after new has been pushed. | |
| DirectivesStack::print(_st); | |
| } | |
| return i; | |
| } | |
| } | |
| DirectivesParser::DirectivesParser(const char* text, outputStream* st, bool silent) | |
| : JSON(text, silent, st), depth(0), current_directive(nullptr), current_directiveset(nullptr), _tmp_top(nullptr), _tmp_depth(0) { | |
| #ifndef PRODUCT | |
| memset(stack, 0, MAX_DEPTH * sizeof(stack[0])); | |
| #endif | |
| parse(); | |
| } | |
| DirectivesParser::~DirectivesParser() { | |
| assert(_tmp_top == nullptr, "Consistency"); | |
| assert(_tmp_depth == 0, "Consistency"); | |
| } | |
| const DirectivesParser::key DirectivesParser::keys[] = { | |
| // name, keytype, allow_array, allowed_mask, set_function | |
| { "c1", type_c1, 0, mask(type_directives), nullptr, UnknownFlagType }, | |
| { "c2", type_c2, 0, mask(type_directives), nullptr, UnknownFlagType }, | |
| { "match", type_match, 1, mask(type_directives), nullptr, UnknownFlagType }, | |
| { "inline", type_inline, 1, mask(type_directives) | mask(type_c1) | mask(type_c2), nullptr, UnknownFlagType }, | |
| // Global flags | |
| #define common_flag_key(name, type, dvalue, compiler) \ | |
| { #name, type_flag, 0, mask(type_directives) | mask(type_c1) | mask(type_c2), &DirectiveSet::set_##name, type##Flag}, | |
| compilerdirectives_common_flags(common_flag_key) | |
| compilerdirectives_c2_flags(common_flag_key) | |
| compilerdirectives_c1_flags(common_flag_key) | |
| #undef common_flag_key | |
| }; | |
| const DirectivesParser::key DirectivesParser::dir_array_key = { | |
| "top level directives array", type_dir_array, 0, 1 // Lowest bit means allow at top level | |
| }; | |
| const DirectivesParser::key DirectivesParser::dir_key = { | |
| "top level directive", type_directives, 0, mask(type_dir_array) | 1 // Lowest bit means allow at top level | |
| }; | |
| const DirectivesParser::key DirectivesParser::value_array_key = { | |
| "value array", type_value_array, 0, UINT_MAX // Allow all, checked by allow_array on other keys, not by allowed_mask from this key | |
| }; | |
| const DirectivesParser::key* DirectivesParser::lookup_key(const char* str, size_t len) { | |
| for (size_t i = 0; i < (sizeof(keys) / sizeof(keys[0])); i++) { | |
| if (strncasecmp(keys[i].name, str, len) == 0) { | |
| return &keys[i]; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| uint DirectivesParser::mask(keytype kt) { | |
| return 1 << (kt + 1); | |
| } | |
| bool DirectivesParser::push_key(const char* str, size_t len) { | |
| bool result = true; | |
| const key* k = lookup_key(str, len); | |
| if (k == nullptr) { | |
| // os::strdup | |
| char* s = NEW_C_HEAP_ARRAY(char, len + 1, mtCompiler); | |
| strncpy(s, str, len); | |
| s[len] = '\0'; | |
| error(KEY_ERROR, "No such key: '%s'.", s); | |
| FREE_C_HEAP_ARRAY(char, s); | |
| return false; | |
| } | |
| return push_key(k); | |
| } | |
| bool DirectivesParser::push_key(const key* k) { | |
| assert(k->allowedmask != 0, "not allowed anywhere?"); | |
| // Exceeding the stack should not be possible with a valid compiler directive, | |
| // and an invalid should abort before this happens | |
| assert(depth < MAX_DEPTH, "exceeded stack depth"); | |
| if (depth >= MAX_DEPTH) { | |
| error(INTERNAL_ERROR, "Stack depth exceeded."); | |
| return false; | |
| } | |
| assert(stack[depth] == nullptr, "element not nulled, something is wrong"); | |
| if (depth == 0 && !(k->allowedmask & 1)) { | |
| error(KEY_ERROR, "Key '%s' not allowed at top level.", k->name); | |
| return false; | |
| } | |
| if (depth > 0) { | |
| const key* prev = stack[depth - 1]; | |
| if (!(k->allowedmask & mask(prev->type))) { | |
| error(KEY_ERROR, "Key '%s' not allowed after '%s' key.", k->name, prev->name); | |
| return false; | |
| } | |
| } | |
| stack[depth] = k; | |
| depth++; | |
| return true; | |
| } | |
| const DirectivesParser::key* DirectivesParser::current_key() { | |
| assert(depth > 0, "getting key from empty stack"); | |
| if (depth == 0) { | |
| return nullptr; | |
| } | |
| return stack[depth - 1]; | |
| } | |
| const DirectivesParser::key* DirectivesParser::pop_key() { | |
| assert(depth > 0, "popping empty stack"); | |
| if (depth == 0) { | |
| error(INTERNAL_ERROR, "Popping empty stack."); | |
| return nullptr; | |
| } | |
| depth--; | |
| const key* k = stack[depth]; | |
| #ifndef PRODUCT | |
| stack[depth] = nullptr; | |
| #endif | |
| return k; | |
| } | |
| bool DirectivesParser::set_option_flag(JSON_TYPE t, JSON_VAL* v, const key* option_key, DirectiveSet* set) { | |
| void (DirectiveSet::*test)(void *args); | |
| test = option_key->set; | |
| switch (t) { | |
| case JSON_TRUE: | |
| if (option_key->flag_type != boolFlag) { | |
| error(VALUE_ERROR, "Cannot use bool value for an %s flag", flag_type_names[option_key->flag_type]); | |
| return false; | |
| } else { | |
| bool val = true; | |
| (set->*test)((void *)&val); | |
| } | |
| break; | |
| case JSON_FALSE: | |
| if (option_key->flag_type != boolFlag) { | |
| error(VALUE_ERROR, "Cannot use bool value for an %s flag", flag_type_names[option_key->flag_type]); | |
| return false; | |
| } else { | |
| bool val = false; | |
| (set->*test)((void *)&val); | |
| } | |
| break; | |
| case JSON_NUMBER_INT: | |
| if (option_key->flag_type == intxFlag) { | |
| intx ival = v->int_value; | |
| (set->*test)((void *)&ival); | |
| } else if (option_key->flag_type == uintxFlag) { | |
| uintx ival = v->uint_value; | |
| (set->*test)((void *)&ival); | |
| } else if (option_key->flag_type == doubleFlag) { | |
| double dval = (double)v->int_value; | |
| (set->*test)((void *)&dval); | |
| } else { | |
| error(VALUE_ERROR, "Cannot use int value for an %s flag", flag_type_names[option_key->flag_type]); | |
| return false; | |
| } | |
| break; | |
| case JSON_NUMBER_FLOAT: | |
| if (option_key->flag_type != doubleFlag) { | |
| error(VALUE_ERROR, "Cannot use double value for an %s flag", flag_type_names[option_key->flag_type]); | |
| return false; | |
| } else { | |
| double dval = v->double_value; | |
| (set->*test)((void *)&dval); | |
| } | |
| break; | |
| case JSON_STRING: | |
| if (option_key->flag_type != ccstrFlag && option_key->flag_type != ccstrlistFlag) { | |
| error(VALUE_ERROR, "Cannot use string value for a %s flag", flag_type_names[option_key->flag_type]); | |
| return false; | |
| } else { | |
| char* s = NEW_C_HEAP_ARRAY(char, v->str.length + 1, mtCompiler); | |
| strncpy(s, v->str.start, v->str.length + 1); | |
| s[v->str.length] = '\0'; | |
| bool valid = true; | |
| if (strncmp(option_key->name, "ControlIntrinsic", 16) == 0) { | |
| ControlIntrinsicValidator validator(s, false/*disabled_all*/); | |
| valid = validator.is_valid(); | |
| if (!valid) { | |
| error(VALUE_ERROR, "Unrecognized intrinsic detected in ControlIntrinsic: %s", validator.what()); | |
| } | |
| } else if (strncmp(option_key->name, "DisableIntrinsic", 16) == 0) { | |
| ControlIntrinsicValidator validator(s, true/*disabled_all*/); | |
| valid = validator.is_valid(); | |
| if (!valid) { | |
| error(VALUE_ERROR, "Unrecognized intrinsic detected in DisableIntrinsic: %s", validator.what()); | |
| } | |
| } | |
| #if !defined(PRODUCT) && defined(COMPILER2) | |
| else if (strncmp(option_key->name, "TraceAutoVectorization", 22) == 0) { | |
| TraceAutoVectorizationTagValidator validator(s, false); | |
| valid = validator.is_valid(); | |
| if (valid) { | |
| set->set_trace_auto_vectorization_tags(validator.tags()); | |
| } else { | |
| error(VALUE_ERROR, "Unrecognized tag name detected in TraceAutoVectorization: %s", validator.what()); | |
| } | |
| } else if (strncmp(option_key->name, "TraceMergeStores", 16) == 0) { | |
| TraceMergeStores::TagValidator validator(s, false); | |
| valid = validator.is_valid(); | |
| if (valid) { | |
| set->set_trace_merge_stores_tags(validator.tags()); | |
| } else { | |
| error(VALUE_ERROR, "Unrecognized tag name detected in TraceMergeStores: %s", validator.what()); | |
| } | |
| } else if (strncmp(option_key->name, "PrintIdealPhase", 15) == 0) { | |
| PhaseNameValidator validator(s); | |
| valid = validator.is_valid(); | |
| if (valid) { | |
| set->set_ideal_phase_name_set(validator.phase_name_set()); | |
| } else { | |
| error(VALUE_ERROR, "Unrecognized phase name detected in PrintIdealPhase: %s", validator.what()); | |
| } | |
| } | |
| #endif | |
| if (!valid) { | |
| FREE_C_HEAP_ARRAY(char, s); | |
| return false; | |
| } | |
| (set->*test)((void *)&s); // Takes ownership. | |
| } | |
| break; | |
| default: | |
| assert(0, "Should not reach here."); | |
| } | |
| return true; | |
| } | |
| bool DirectivesParser::set_option(JSON_TYPE t, JSON_VAL* v) { | |
| const key* option_key = pop_key(); | |
| const key* enclosing_key = current_key(); | |
| if (option_key->type == value_array_key.type) { | |
| // Multi value array, we are really setting the value | |
| // for the key one step further up. | |
| option_key = pop_key(); | |
| enclosing_key = current_key(); | |
| // Repush option_key and multi value marker, since | |
| // we need to keep them until all multi values are set. | |
| push_key(option_key); | |
| push_key(&value_array_key); | |
| } | |
| switch (option_key->type) { | |
| case type_flag: | |
| { | |
| if (current_directiveset == nullptr) { | |
| assert(depth == 2, "Must not have active directive set"); | |
| if (!set_option_flag(t, v, option_key, current_directive->_c1_store)) { | |
| return false; | |
| } | |
| if (!set_option_flag(t, v, option_key, current_directive->_c2_store)) { | |
| return false; | |
| } | |
| } else { | |
| assert(depth > 2, "Must have active current directive set"); | |
| if (!set_option_flag(t, v, option_key, current_directiveset)) { | |
| return false; | |
| } | |
| } | |
| break; | |
| } | |
| case type_match: | |
| if (t != JSON_STRING) { | |
| error(VALUE_ERROR, "Key of type %s needs a value of type string", option_key->name); | |
| return false; | |
| } | |
| if (enclosing_key->type != type_directives) { | |
| error(SYNTAX_ERROR, "Match keyword can only exist inside a directive"); | |
| return false; | |
| } | |
| { | |
| char* s = NEW_C_HEAP_ARRAY(char, v->str.length + 1, mtCompiler); | |
| strncpy(s, v->str.start, v->str.length); | |
| s[v->str.length] = '\0'; | |
| const char* error_msg = nullptr; | |
| if (!current_directive->add_match(s, error_msg)) { | |
| assert (error_msg != nullptr, "Must have valid error message"); | |
| error(VALUE_ERROR, "Method pattern error: %s", error_msg); | |
| } | |
| FREE_C_HEAP_ARRAY(char, s); | |
| } | |
| break; | |
| case type_inline: | |
| if (t != JSON_STRING) { | |
| error(VALUE_ERROR, "Key of type %s needs a value of type string", option_key->name); | |
| return false; | |
| } | |
| { | |
| //char* s = strndup(v->str.start, v->str.length); | |
| char* s = NEW_C_HEAP_ARRAY(char, v->str.length + 1, mtCompiler); | |
| strncpy(s, v->str.start, v->str.length); | |
| s[v->str.length] = '\0'; | |
| const char* error_msg = nullptr; | |
| if (current_directiveset == nullptr) { | |
| if (current_directive->_c1_store->parse_and_add_inline(s, error_msg)) { | |
| if (!current_directive->_c2_store->parse_and_add_inline(s, error_msg)) { | |
| assert (error_msg != nullptr, "Must have valid error message"); | |
| error(VALUE_ERROR, "Method pattern error: %s", error_msg); | |
| } | |
| } else { | |
| assert (error_msg != nullptr, "Must have valid error message"); | |
| error(VALUE_ERROR, "Method pattern error: %s", error_msg); | |
| } | |
| } else { | |
| if (!current_directiveset->parse_and_add_inline(s, error_msg)) { | |
| assert (error_msg != nullptr, "Must have valid error message"); | |
| error(VALUE_ERROR, "Method pattern error: %s", error_msg); | |
| } | |
| } | |
| FREE_C_HEAP_ARRAY(char, s); | |
| } | |
| break; | |
| case type_c1: | |
| current_directiveset = current_directive->_c1_store; | |
| if (t != JSON_TRUE && t != JSON_FALSE) { | |
| error(VALUE_ERROR, "Key of type %s needs a true or false value", option_key->name); | |
| return false; | |
| } | |
| break; | |
| case type_c2: | |
| current_directiveset = current_directive->_c2_store; | |
| if (t != JSON_TRUE && t != JSON_FALSE) { | |
| error(VALUE_ERROR, "Key of type %s needs a true or false value", option_key->name); | |
| return false; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| return true; | |
| } | |
| bool DirectivesParser::callback(JSON_TYPE t, JSON_VAL* v, uint rlimit) { | |
| const key* k; | |
| if (depth == 0) { | |
| switch (t) { | |
| case JSON_ARRAY_BEGIN: | |
| return push_key(&dir_array_key); | |
| case JSON_OBJECT_BEGIN: | |
| // push synthetic dir_array | |
| push_key(&dir_array_key); | |
| assert(depth == 1, "Make sure the stack are aligned with the directives"); | |
| break; | |
| default: | |
| error(SYNTAX_ERROR, "DirectivesParser can only start with an array containing directive objects, or one single directive."); | |
| return false; | |
| } | |
| } | |
| if (depth == 1) { | |
| switch (t) { | |
| case JSON_OBJECT_BEGIN: | |
| // Parsing a new directive. | |
| current_directive = new CompilerDirectives(); | |
| return push_key(&dir_key); | |
| case JSON_ARRAY_END: | |
| k = pop_key(); | |
| if (k->type != type_dir_array) { | |
| error(SYNTAX_ERROR, "Expected end of directives array"); | |
| return false; | |
| } | |
| return true; | |
| default: | |
| error(SYNTAX_ERROR, "DirectivesParser can only start with an array containing directive objects, or one single directive."); | |
| return false; | |
| } | |
| } else { | |
| switch (t) { | |
| case JSON_OBJECT_BEGIN: | |
| k = current_key(); | |
| switch (k->type) { | |
| case type_c1: | |
| current_directiveset = current_directive->_c1_store; | |
| return true; | |
| case type_c2: | |
| current_directiveset = current_directive->_c2_store; | |
| return true; | |
| case type_dir_array: | |
| return push_key(&dir_key); | |
| default: | |
| error(SYNTAX_ERROR, "The key '%s' does not allow an object to follow.", k->name); | |
| return false; | |
| } | |
| return false; | |
| case JSON_OBJECT_END: | |
| k = pop_key(); | |
| switch (k->type) { | |
| case type_c1: | |
| case type_c2: | |
| // This is how we now if options apply to a single or both directive sets | |
| current_directiveset = nullptr; | |
| break; | |
| case type_directives: | |
| // Check, finish and push to stack! | |
| if (current_directive->match() == nullptr) { | |
| error(INTERNAL_ERROR, "Directive missing required match."); | |
| return false; | |
| } | |
| current_directive->finalize(_st); | |
| push_tmp(current_directive); | |
| current_directive = nullptr; | |
| break; | |
| default: | |
| error(INTERNAL_ERROR, "Object end with wrong key type on stack: %s.", k->name); | |
| ShouldNotReachHere(); | |
| return false; | |
| } | |
| return true; | |
| case JSON_ARRAY_BEGIN: | |
| k = current_key(); | |
| if (!(k->allow_array_value)) { | |
| if (k->type == type_dir_array) { | |
| error(SYNTAX_ERROR, "Array not allowed inside top level array, expected directive object."); | |
| } else { | |
| error(VALUE_ERROR, "The key '%s' does not allow an array of values.", k->name); | |
| } | |
| return false; | |
| } | |
| return push_key(&value_array_key); | |
| case JSON_ARRAY_END: | |
| k = pop_key(); // Pop multi value marker | |
| assert(k->type == value_array_key.type, "array end for level != 0 should terminate multi value"); | |
| k = pop_key(); // Pop key for option that was set | |
| return true; | |
| case JSON_KEY: | |
| return push_key(v->str.start, v->str.length); | |
| case JSON_STRING: | |
| case JSON_NUMBER_INT: | |
| case JSON_NUMBER_FLOAT: | |
| case JSON_TRUE: | |
| case JSON_FALSE: | |
| case JSON_NULL: | |
| return set_option(t, v); | |
| default: | |
| error(INTERNAL_ERROR, "Unknown JSON type: %d.", t); | |
| ShouldNotReachHere(); | |
| return false; | |
| } | |
| } | |
| } | |