| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| #include "vm/flags.h" | |
| #include "platform/assert.h" | |
| #include "vm/heap/page.h" | |
| #include "vm/isolate.h" | |
| #include "vm/json_stream.h" | |
| #include "vm/os.h" | |
| #include "vm/version.h" | |
| namespace dart { | |
| DEFINE_FLAG(bool, print_flags, false, "Print flags as they are being parsed."); | |
| DEFINE_FLAG(bool, | |
| ignore_unrecognized_flags, | |
| false, | |
| "Ignore unrecognized flags."); | |
| #define PRODUCT_FLAG_MACRO(name, type, default_value, comment) \ | |
| type FLAG_##name = \ | |
| Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | |
| #if defined(DEBUG) | |
| #define DEBUG_FLAG_MACRO(name, type, default_value, comment) \ | |
| type FLAG_##name = \ | |
| Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | |
| #else // defined(DEBUG) | |
| #define DEBUG_FLAG_MACRO(name, type, default_value, comment) | |
| #endif // defined(DEBUG) | |
| #if defined(PRODUCT) && defined(DART_PRECOMPILED_RUNTIME) | |
| // Nothing to be done for the product flag definitions. | |
| #define RELEASE_FLAG_MACRO(name, product_value, type, default_value, comment) | |
| // Nothing to be done for the precompilation flag definitions. | |
| #define PRECOMPILE_FLAG_MACRO(name, pre_value, product_value, type, \ | |
| default_value, comment) | |
| #elif defined(PRODUCT) // !PRECOMPILED | |
| // Nothing to be done for the product flag definitions. | |
| #define RELEASE_FLAG_MACRO(name, product_value, type, default_value, comment) | |
| // Nothing to be done for the precompilation flag definitions. | |
| #define PRECOMPILE_FLAG_MACRO(name, pre_value, product_value, type, \ | |
| default_value, comment) | |
| #elif defined(DART_PRECOMPILED_RUNTIME) // !PRODUCT | |
| #define RELEASE_FLAG_MACRO(name, product_value, type, default_value, comment) \ | |
| type FLAG_##name = \ | |
| Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | |
| // Nothing to be done for the precompilation flag definitions. | |
| #define PRECOMPILE_FLAG_MACRO(name, pre_value, product_value, type, \ | |
| default_value, comment) | |
| #else // !PRODUCT && !PRECOMPILED | |
| #define RELEASE_FLAG_MACRO(name, product_value, type, default_value, comment) \ | |
| type FLAG_##name = \ | |
| Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | |
| #define PRECOMPILE_FLAG_MACRO(name, pre_value, product_value, type, \ | |
| default_value, comment) \ | |
| type FLAG_##name = \ | |
| Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | |
| #endif | |
| // Define all of the non-product flags here. | |
| FLAG_LIST(PRODUCT_FLAG_MACRO, | |
| RELEASE_FLAG_MACRO, | |
| PRECOMPILE_FLAG_MACRO, | |
| DEBUG_FLAG_MACRO) | |
| #undef PRODUCT_FLAG_MACRO | |
| #undef RELEASE_FLAG_MACRO | |
| #undef PRECOMPILE_FLAG_MACRO | |
| #undef DEBUG_FLAG_MACRO | |
| #if defined(DART_PRECOMPILER) | |
| #if defined(TARGET_USES_ADDRESS_SANITIZER) | |
| constexpr bool kDefaultTargetAddressSanitizer = true; | |
| #else | |
| constexpr bool kDefaultTargetAddressSanitizer = false; | |
| #endif | |
| DEFINE_FLAG(bool, | |
| target_address_sanitizer, | |
| kDefaultTargetAddressSanitizer, | |
| "Generate Dart code compatible with Address Sanitizer"); | |
| #if defined(TARGET_USES_MEMORY_SANITIZER) | |
| constexpr bool kDefaultTargetMemorySanitizer = true; | |
| #else | |
| constexpr bool kDefaultTargetMemorySanitizer = false; | |
| #endif | |
| DEFINE_FLAG(bool, | |
| target_memory_sanitizer, | |
| kDefaultTargetMemorySanitizer, | |
| "Generate Dart code compatible with Memory Sanitizer"); | |
| #if defined(TARGET_USES_THREAD_SANITIZER) | |
| constexpr bool kDefaultTargetThreadSanitizer = true; | |
| #else | |
| constexpr bool kDefaultTargetThreadSanitizer = false; | |
| #endif | |
| DEFINE_FLAG(bool, | |
| target_thread_sanitizer, | |
| kDefaultTargetThreadSanitizer, | |
| "Generate Dart code compatible with Thread Sanitizer"); | |
| #endif | |
| static bool IsMainOrDevChannel() { | |
| return strstr("|main|dev|", Version::Channel()) != nullptr; | |
| } | |
| static void EnableExperimentSharedData(bool value) { | |
| if (value && !IsMainOrDevChannel()) { | |
| FATAL( | |
| "Shared memory multithreading in only available for " | |
| "experimentation in dev or main"); | |
| } | |
| dart::FLAG_experimental_shared_data = value; | |
| } | |
| bool FLAG_experimental_shared_data = false; | |
| DEFINE_FLAG_HANDLER(EnableExperimentSharedData, | |
| experimental_shared_data, | |
| "Enable experiment to share data between isolates."); | |
| bool Flags::initialized_ = false; | |
| // List of registered flags. | |
| Flag** Flags::flags_ = nullptr; | |
| intptr_t Flags::capacity_ = 0; | |
| intptr_t Flags::num_flags_ = 0; | |
| class Flag { | |
| public: | |
| enum FlagType { | |
| kBoolean, | |
| kInteger, | |
| kUint64, | |
| kString, | |
| kFlagHandler, | |
| kOptionHandler, | |
| kNumFlagTypes | |
| }; | |
| Flag(const char* name, const char* comment, void* addr, FlagType type) | |
| : name_(name), comment_(comment), addr_(addr), type_(type) {} | |
| Flag(const char* name, const char* comment, FlagHandler handler) | |
| : name_(name), | |
| comment_(comment), | |
| flag_handler_(handler), | |
| type_(kFlagHandler) {} | |
| Flag(const char* name, const char* comment, OptionHandler handler) | |
| : name_(name), | |
| comment_(comment), | |
| option_handler_(handler), | |
| type_(kOptionHandler) {} | |
| void Print() { | |
| if (IsUnrecognized()) { | |
| OS::PrintErr("%s: unrecognized\n", name_); | |
| return; | |
| } | |
| switch (type_) { | |
| case kBoolean: { | |
| OS::Print("%s: %s (%s)\n", name_, *this->bool_ptr_ ? "true" : "false", | |
| comment_); | |
| break; | |
| } | |
| case kInteger: { | |
| OS::Print("%s: %d (%s)\n", name_, *this->int_ptr_, comment_); | |
| break; | |
| } | |
| case kUint64: { | |
| OS::Print("%s: %" Pu64 " (%s)\n", name_, *this->uint64_ptr_, comment_); | |
| break; | |
| } | |
| case kString: { | |
| if (*this->charp_ptr_ != nullptr) { | |
| OS::Print("%s: '%s' (%s)\n", name_, *this->charp_ptr_, comment_); | |
| } else { | |
| OS::Print("%s: (null) (%s)\n", name_, comment_); | |
| } | |
| break; | |
| } | |
| case kOptionHandler: | |
| case kFlagHandler: { | |
| OS::Print("%s: (%s)\n", name_, comment_); | |
| break; | |
| } | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| } | |
| bool IsUnrecognized() const { | |
| return (type_ == kBoolean) && (bool_ptr_ == nullptr); | |
| } | |
| const char* name_; | |
| const char* comment_; | |
| // For kString, kOptionHandler, kFlagHandler flags this stores the copy | |
| // of the original flag value passed to SetFlagFromString | |
| CStringUniquePtr string_value_; | |
| union { | |
| void* addr_; | |
| bool* bool_ptr_; | |
| int* int_ptr_; | |
| uint64_t* uint64_ptr_; | |
| charp* charp_ptr_; | |
| FlagHandler flag_handler_; | |
| OptionHandler option_handler_; | |
| }; | |
| const FlagType type_; | |
| bool changed_ = false; | |
| }; | |
| Flag* Flags::Lookup(const char* name) { | |
| for (intptr_t i = 0; i < num_flags_; i++) { | |
| Flag* flag = flags_[i]; | |
| if (strcmp(flag->name_, name) == 0) { | |
| return flag; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| bool Flags::IsSet(const char* name) { | |
| Flag* flag = Lookup(name); | |
| return (flag != nullptr) && (flag->type_ == Flag::kBoolean) && | |
| (flag->bool_ptr_ != nullptr) && (*flag->bool_ptr_ == true); | |
| } | |
| void Flags::Cleanup() { | |
| ASSERT(initialized_); | |
| initialized_ = false; | |
| } | |
| void Flags::AddFlag(Flag* flag) { | |
| ASSERT(!initialized_); | |
| if (num_flags_ == capacity_) { | |
| if (flags_ == nullptr) { | |
| capacity_ = 256; | |
| flags_ = new Flag*[capacity_]; | |
| } else { | |
| intptr_t new_capacity = capacity_ * 2; | |
| Flag** new_flags = new Flag*[new_capacity]; | |
| for (intptr_t i = 0; i < num_flags_; i++) { | |
| new_flags[i] = flags_[i]; | |
| } | |
| delete[] flags_; | |
| flags_ = new_flags; | |
| capacity_ = new_capacity; | |
| } | |
| } | |
| flags_[num_flags_++] = flag; | |
| } | |
| bool Flags::Register_bool(bool* addr, | |
| const char* name, | |
| bool default_value, | |
| const char* comment) { | |
| Flag* flag = Lookup(name); | |
| if (flag != nullptr) { | |
| ASSERT(flag->IsUnrecognized()); | |
| return default_value; | |
| } | |
| flag = new Flag(name, comment, addr, Flag::kBoolean); | |
| AddFlag(flag); | |
| return default_value; | |
| } | |
| int Flags::Register_int(int* addr, | |
| const char* name, | |
| int default_value, | |
| const char* comment) { | |
| ASSERT(Lookup(name) == nullptr); | |
| Flag* flag = new Flag(name, comment, addr, Flag::kInteger); | |
| AddFlag(flag); | |
| return default_value; | |
| } | |
| uint64_t Flags::Register_uint64_t(uint64_t* addr, | |
| const char* name, | |
| uint64_t default_value, | |
| const char* comment) { | |
| ASSERT(Lookup(name) == nullptr); | |
| Flag* flag = new Flag(name, comment, addr, Flag::kUint64); | |
| AddFlag(flag); | |
| return default_value; | |
| } | |
| const char* Flags::Register_charp(charp* addr, | |
| const char* name, | |
| const char* default_value, | |
| const char* comment) { | |
| ASSERT(Lookup(name) == nullptr); | |
| Flag* flag = new Flag(name, comment, addr, Flag::kString); | |
| AddFlag(flag); | |
| return default_value; | |
| } | |
| bool Flags::RegisterFlagHandler(FlagHandler handler, | |
| const char* name, | |
| const char* comment) { | |
| ASSERT(Lookup(name) == nullptr); | |
| Flag* flag = new Flag(name, comment, handler); | |
| AddFlag(flag); | |
| return false; | |
| } | |
| bool Flags::RegisterOptionHandler(OptionHandler handler, | |
| const char* name, | |
| const char* comment) { | |
| ASSERT(Lookup(name) == nullptr); | |
| Flag* flag = new Flag(name, comment, handler); | |
| AddFlag(flag); | |
| return false; | |
| } | |
| static void Normalize(char* s) { | |
| intptr_t len = strlen(s); | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (s[i] == '-') { | |
| s[i] = '_'; | |
| } | |
| } | |
| } | |
| bool Flags::SetFlagFromString(Flag* flag, const char* argument) { | |
| ASSERT(!flag->IsUnrecognized()); | |
| switch (flag->type_) { | |
| case Flag::kBoolean: { | |
| if (strcmp(argument, "true") == 0) { | |
| *flag->bool_ptr_ = true; | |
| } else if (strcmp(argument, "false") == 0) { | |
| *flag->bool_ptr_ = false; | |
| } else { | |
| return false; | |
| } | |
| break; | |
| } | |
| case Flag::kString: { | |
| flag->string_value_.reset(argument == nullptr ? nullptr | |
| : Utils::StrDup(argument)); | |
| *flag->charp_ptr_ = flag->string_value_.get(); | |
| break; | |
| } | |
| case Flag::kInteger: { | |
| char* endptr = nullptr; | |
| const intptr_t len = strlen(argument); | |
| int base = 10; | |
| if ((len > 2) && (argument[0] == '0') && (argument[1] == 'x')) { | |
| base = 16; | |
| } | |
| int val = strtol(argument, &endptr, base); | |
| if (endptr == argument + len) { | |
| *flag->int_ptr_ = val; | |
| } else { | |
| return false; | |
| } | |
| break; | |
| } | |
| case Flag::kUint64: { | |
| char* endptr = nullptr; | |
| const intptr_t len = strlen(argument); | |
| int base = 10; | |
| if ((len > 2) && (argument[0] == '0') && (argument[1] == 'x')) { | |
| base = 16; | |
| } | |
| int64_t val = strtoll(argument, &endptr, base); | |
| if (endptr == argument + len) { | |
| *flag->uint64_ptr_ = static_cast<uint64_t>(val); | |
| } else { | |
| return false; | |
| } | |
| break; | |
| } | |
| case Flag::kFlagHandler: { | |
| if (strcmp(argument, "true") == 0) { | |
| (flag->flag_handler_)(true); | |
| } else if (strcmp(argument, "false") == 0) { | |
| (flag->flag_handler_)(false); | |
| } else { | |
| return false; | |
| } | |
| flag->string_value_.reset(Utils::StrDup(argument)); | |
| break; | |
| } | |
| case Flag::kOptionHandler: { | |
| flag->string_value_.reset(Utils::StrDup(argument)); | |
| (flag->option_handler_)(argument); | |
| break; | |
| } | |
| default: { | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| } | |
| flag->changed_ = true; | |
| return true; | |
| } | |
| void Flags::Parse(const char* option) { | |
| // Find the beginning of the option argument, if it exists. | |
| const char* equals = option; | |
| while ((*equals != '\0') && (*equals != '=')) { | |
| equals++; | |
| } | |
| const char* argument = nullptr; | |
| // Determine if this is an option argument. | |
| if (*equals != '=') { | |
| // No explicit option argument. Determine if there is a "no_" prefix | |
| // preceding the name. | |
| const char* const kNo1Prefix = "no_"; | |
| const char* const kNo2Prefix = "no-"; | |
| const intptr_t kNo1PrefixLen = strlen(kNo1Prefix); | |
| const intptr_t kNo2PrefixLen = strlen(kNo2Prefix); | |
| if (strncmp(option, kNo1Prefix, kNo1PrefixLen) == 0) { | |
| option += kNo1PrefixLen; // Skip the "no_" when looking up the name. | |
| argument = "false"; | |
| } else if (strncmp(option, kNo2Prefix, kNo2PrefixLen) == 0) { | |
| option += kNo2PrefixLen; // Skip the "no-" when looking up the name. | |
| argument = "false"; | |
| } else { | |
| argument = "true"; | |
| } | |
| } else { | |
| // The argument for the option starts right after the equals sign. | |
| argument = equals + 1; | |
| } | |
| // Initialize the flag name. | |
| intptr_t name_len = equals - option; | |
| char* name = new char[name_len + 1]; | |
| strncpy(name, option, name_len); | |
| name[name_len] = '\0'; | |
| Normalize(name); | |
| Flag* flag = Flags::Lookup(name); | |
| if (flag == nullptr) { | |
| // Collect unrecognized flags. | |
| char* new_flag = new char[name_len + 1]; | |
| strncpy(new_flag, option, name_len); | |
| new_flag[name_len] = '\0'; | |
| Normalize(new_flag); // Or a later lookup may fail. | |
| Flags::Register_bool(nullptr, new_flag, true, nullptr); | |
| } else { | |
| // Only set values for recognized flags, skip collected | |
| // unrecognized flags. | |
| if (!flag->IsUnrecognized()) { | |
| if (!SetFlagFromString(flag, argument)) { | |
| OS::PrintErr("Ignoring flag: %s is an invalid value for flag %s\n", | |
| argument, name); | |
| } | |
| } | |
| } | |
| delete[] name; | |
| } | |
| static bool IsValidFlag(const char* name, | |
| const char* prefix, | |
| intptr_t prefix_length) { | |
| intptr_t name_length = strlen(name); | |
| return ((name_length > prefix_length) && | |
| (strncmp(name, prefix, prefix_length) == 0)); | |
| } | |
| int Flags::CompareFlagNames(const void* left, const void* right) { | |
| const Flag* left_flag = *reinterpret_cast<const Flag* const*>(left); | |
| const Flag* right_flag = *reinterpret_cast<const Flag* const*>(right); | |
| return strcmp(left_flag->name_, right_flag->name_); | |
| } | |
| char* Flags::ProcessCommandLineFlags(int number_of_vm_flags, | |
| const char** vm_flags) { | |
| if (initialized_) { | |
| return Utils::StrDup("Flags already set"); | |
| } | |
| qsort(flags_, num_flags_, sizeof flags_[0], CompareFlagNames); | |
| const char* const kPrefix = "--"; | |
| const intptr_t kPrefixLen = strlen(kPrefix); | |
| int i = 0; | |
| while ((i < number_of_vm_flags) && | |
| IsValidFlag(vm_flags[i], kPrefix, kPrefixLen)) { | |
| const char* option = vm_flags[i] + kPrefixLen; | |
| Parse(option); | |
| i++; | |
| } | |
| if (!FLAG_ignore_unrecognized_flags) { | |
| int unrecognized_count = 0; | |
| TextBuffer error(64); | |
| for (intptr_t j = 0; j < num_flags_; j++) { | |
| Flag* flag = flags_[j]; | |
| if (flag->IsUnrecognized()) { | |
| if (unrecognized_count == 0) { | |
| error.Printf("Unrecognized flags: %s", flag->name_); | |
| } else { | |
| error.Printf(", %s", flag->name_); | |
| } | |
| unrecognized_count++; | |
| } | |
| } | |
| if (unrecognized_count > 0) { | |
| return error.Steal(); | |
| } | |
| } | |
| if (FLAG_print_flags) { | |
| PrintFlags(); | |
| } | |
| char* error = CheckFlags(); | |
| if (error != nullptr) { | |
| return error; | |
| } | |
| initialized_ = true; | |
| return nullptr; | |
| } | |
| bool Flags::SetFlag(const char* name, const char* value, const char** error) { | |
| Flag* flag = Lookup(name); | |
| if (flag == nullptr) { | |
| *error = "Cannot set flag: flag not found"; | |
| return false; | |
| } | |
| if (!SetFlagFromString(flag, value)) { | |
| *error = "Cannot set flag: invalid value"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| char* Flags::CheckFlags() { | |
| if (((FLAG_target_address_sanitizer ? 1 : 0) + | |
| (FLAG_target_memory_sanitizer ? 1 : 0) + | |
| (FLAG_target_thread_sanitizer ? 1 : 0)) > 1) { | |
| return Utils::StrDup("Can only target one sanitizer at a time"); | |
| } | |
| if (FLAG_scavenger_tasks < -1) { | |
| return Utils::StrDup("Invalid scavenger_tasks"); | |
| } | |
| if (FLAG_marker_tasks < 0) { | |
| return Utils::StrDup("Invalid marker_tasks"); | |
| } | |
| if (FLAG_compactor_tasks < 0) { | |
| return Utils::StrDup("Invalid compactor_tasks"); | |
| } | |
| if (FLAG_new_gen_semi_initial_size * MB < Page::kPageSize) { | |
| return Utils::StrDup("Invalid new_gen_semi_initial_size"); | |
| } | |
| if (FLAG_new_gen_semi_max_size * MB < Page::kPageSize) { | |
| return Utils::StrDup("Invalid new_gen_semi_max_size"); | |
| } | |
| return nullptr; | |
| } | |
| void Flags::PrintFlags() { | |
| OS::Print("Flag settings:\n"); | |
| for (intptr_t i = 0; i < num_flags_; ++i) { | |
| flags_[i]->Print(); | |
| } | |
| } | |
| #ifndef PRODUCT | |
| void Flags::PrintFlagToJSONArray(JSONArray* jsarr, const Flag* flag) { | |
| if (flag->IsUnrecognized()) { | |
| return; | |
| } | |
| JSONObject jsflag(jsarr); | |
| jsflag.AddProperty("name", flag->name_); | |
| jsflag.AddProperty("comment", flag->comment_); | |
| jsflag.AddProperty("modified", flag->changed_); | |
| switch (flag->type_) { | |
| case Flag::kBoolean: { | |
| jsflag.AddProperty("_flagType", "Bool"); | |
| jsflag.AddProperty("valueAsString", | |
| (*flag->bool_ptr_ ? "true" : "false")); | |
| break; | |
| } | |
| case Flag::kInteger: { | |
| jsflag.AddProperty("_flagType", "Int"); | |
| jsflag.AddPropertyF("valueAsString", "%d", *flag->int_ptr_); | |
| break; | |
| } | |
| case Flag::kUint64: { | |
| jsflag.AddProperty("_flagType", "UInt64"); | |
| jsflag.AddPropertyF("valueAsString", "%" Pu64, *flag->uint64_ptr_); | |
| break; | |
| } | |
| case Flag::kString: { | |
| jsflag.AddProperty("_flagType", "String"); | |
| if (flag->charp_ptr_ != nullptr) { | |
| jsflag.AddPropertyF("valueAsString", "%s", *flag->charp_ptr_); | |
| } else { | |
| // valueAsString missing means nullptr. | |
| } | |
| break; | |
| } | |
| case Flag::kFlagHandler: { | |
| jsflag.AddProperty("_flagType", "Bool"); | |
| const char* value = flag->string_value_.get(); | |
| jsflag.AddProperty("valueAsString", value == nullptr ? "false" : value); | |
| break; | |
| } | |
| case Flag::kOptionHandler: { | |
| jsflag.AddProperty("_flagType", "String"); | |
| if (flag->string_value_ != nullptr) { | |
| jsflag.AddProperty("valueAsString", flag->string_value_.get()); | |
| } else { | |
| // valueAsString missing means nullptr. | |
| } | |
| break; | |
| } | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| } | |
| void Flags::PrintJSON(JSONStream* js) { | |
| JSONObject jsobj(js); | |
| jsobj.AddProperty("type", "FlagList"); | |
| JSONArray jsarr(&jsobj, "flags"); | |
| for (intptr_t i = 0; i < num_flags_; ++i) { | |
| PrintFlagToJSONArray(&jsarr, flags_[i]); | |
| } | |
| } | |
| #endif // !PRODUCT | |
| } // namespace dart |