From aaebaa0121be3b9d9f13630585304482cbcaeb4b Mon Sep 17 00:00:00 2001 From: Igor Murashkin Date: Mon, 26 Jan 2015 10:55:53 -0800 Subject: [PATCH] art: Refactor RuntimeOptions/ParsedOptions Refactor the RuntimeOptions to be a type-safe map (VariantMap, see runtime_options.h) and the ParsedOptions to delegate the parsing to CmdlineParser (see cmdline/cmdline_parser.h). This is the start of a command line parsing refactor, and may include more in the future (dex2oat, patchoat, etc). For more details of the command line parsing generator usage see cmdline/README.md Change-Id: Ic67c6bca5e1f33bf2ec60e2e3ff8c366bab91563 --- build/Android.gtest.mk | 2 + cmdline/README.md | 245 +++++ cmdline/cmdline_parse_result.h | 138 +++ cmdline/cmdline_parser.h | 635 +++++++++++++ cmdline/cmdline_parser_test.cc | 534 +++++++++++ cmdline/cmdline_result.h | 103 +++ cmdline/cmdline_type_parser.h | 76 ++ cmdline/cmdline_types.h | 820 +++++++++++++++++ cmdline/detail/cmdline_debug_detail.h | 40 + cmdline/detail/cmdline_parse_argument_detail.h | 503 +++++++++++ cmdline/detail/cmdline_parser_detail.h | 128 +++ cmdline/memory_representation.h | 71 ++ cmdline/token_range.h | 425 +++++++++ cmdline/unit.h | 35 + runtime/Android.mk | 3 + runtime/base/variant_map.h | 453 ++++++++++ runtime/base/variant_map_test.cc | 168 ++++ runtime/common_runtime_test.cc | 1 + runtime/common_runtime_test.h | 8 +- runtime/gc/collector_type.h | 13 + runtime/gc/heap.cc | 4 +- runtime/gc/heap.h | 2 +- runtime/gc/space/large_object_space.h | 8 +- runtime/globals.h | 12 +- runtime/java_vm_ext.cc | 14 +- runtime/java_vm_ext.h | 3 +- runtime/jdwp/jdwp.h | 2 + runtime/jdwp/jdwp_main.cc | 12 + runtime/parsed_options.cc | 1136 +++++++++--------------- runtime/parsed_options.h | 97 +- runtime/parsed_options_test.cc | 44 +- runtime/runtime.cc | 179 ++-- runtime/runtime_options.cc | 32 + runtime/runtime_options.def | 119 +++ runtime/runtime_options.h | 83 ++ runtime/trace.cc | 14 +- runtime/utils.h | 7 + 37 files changed, 5256 insertions(+), 913 deletions(-) create mode 100644 cmdline/README.md create mode 100644 cmdline/cmdline_parse_result.h create mode 100644 cmdline/cmdline_parser.h create mode 100644 cmdline/cmdline_parser_test.cc create mode 100644 cmdline/cmdline_result.h create mode 100644 cmdline/cmdline_type_parser.h create mode 100644 cmdline/cmdline_types.h create mode 100644 cmdline/detail/cmdline_debug_detail.h create mode 100644 cmdline/detail/cmdline_parse_argument_detail.h create mode 100644 cmdline/detail/cmdline_parser_detail.h create mode 100644 cmdline/memory_representation.h create mode 100644 cmdline/token_range.h create mode 100644 cmdline/unit.h create mode 100644 runtime/base/variant_map.h create mode 100644 runtime/base/variant_map_test.cc create mode 100644 runtime/runtime_options.cc create mode 100644 runtime/runtime_options.def create mode 100644 runtime/runtime_options.h diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index b4eb0c016..06d258de8 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -81,6 +81,7 @@ ART_GTEST_imgdiag_test_TARGET_DEPS := \ LOCAL_PATH := art RUNTIME_GTEST_COMMON_SRC_FILES := \ + cmdline/cmdline_parser_test.cc \ imgdiag/imgdiag_test.cc \ runtime/arch/arch_test.cc \ runtime/arch/instruction_set_test.cc \ @@ -103,6 +104,7 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ runtime/base/scoped_flock_test.cc \ runtime/base/stringprintf_test.cc \ runtime/base/timing_logger_test.cc \ + runtime/base/variant_map_test.cc \ runtime/base/unix_file/fd_file_test.cc \ runtime/class_linker_test.cc \ runtime/dex_file_test.cc \ diff --git a/cmdline/README.md b/cmdline/README.md new file mode 100644 index 000000000..8cac77f82 --- /dev/null +++ b/cmdline/README.md @@ -0,0 +1,245 @@ +Cmdline +=================== + +Introduction +------------- +This directory contains the classes that do common command line tool initialization and parsing. The +long term goal is eventually for all `art` command-line tools to be using these helpers. + +---------- + + +## Cmdline Parser +------------- + +The `CmdlineParser` class provides a fluent interface using a domain-specific language to quickly +generate a type-safe value parser that process a user-provided list of strings (`argv`). Currently, +it can parse a string into a `VariantMap`, although in the future it might be desirable to parse +into any struct of any field. + +To use, create a `CmdlineParser::Builder` and then chain the `Define` methods together with +`WithType` and `IntoXX` methods. + +### Quick Start +For example, to save the values into a user-defined variant map: + +``` +struct FruitVariantMap : VariantMap { + static const Key Apple; + static const Key Orange; + static const Key Help; +}; +// Note that some template boilerplate has been avoided for clarity. +// See variant_map_test.cc for how to completely define a custom map. + +using FruitParser = CmdlineParser; + +FruitParser MakeParser() { + auto&& builder = FruitParser::Builder(); + builder. + .Define("--help") + .IntoKey(FruitVariantMap::Help) + Define("--apple:_") + .WithType() + .IntoKey(FruitVariantMap::Apple) + .Define("--orange:_") + .WithType() + .WithRange(0.0, 1.0) + .IntoKey(FruitVariantMap::Orange); + + return builder.Build(); +} + +int main(char** argv, int argc) { + auto parser = MakeParser(); + auto result = parser.parse(argv, argc)); + if (result.isError()) { + std::cerr << result.getMessage() << std::endl; + return EXIT_FAILURE; + } + auto map = parser.GetArgumentsMap(); + std::cout << "Help? " << map.GetOrDefault(FruitVariantMap::Help) << std::endl; + std::cout << "Apple? " << map.GetOrDefault(FruitVariantMap::Apple) << std::endl; + std::cout << "Orange? " << map.GetOrDefault(FruitVariantMap::Orange) << std::endl; + + return EXIT_SUCCESS; +} +``` + +In the above code sample, we define a parser which is capable of parsing something like `--help +--apple:123 --orange:0.456` . It will error out automatically if invalid flags are given, or if the +appropriate flags are given but of the the wrong type/range. So for example, `--foo` will not parse +(invalid argument), neither will `--apple:fruit` (fruit is not an int) nor `--orange:1234` (1234 is +out of range of [0.0, 1.0]) + +### Argument Definitions in Detail +#### Define method +The 'Define' method takes one or more aliases for the argument. Common examples might be `{"-h", +"--help"}` where both `--help` and `-h` are aliases for the same argument. + +The simplest kind of argument just tests for presence, but we often want to parse out a particular +type of value (such as an int or double as in the above `FruitVariantMap` example). To do that, a +_wildcard_ must be used to denote the location within the token that the type will be parsed out of. + +For example with `-orange:_` the parse would know to check all tokens in an `argv` list for the +`-orange:` prefix and then strip it, leaving only the remains to be parsed. + +#### WithType method (optional) +After an argument definition is provided, the parser builder needs to know what type the argument +will be in order to provide the type safety and make sure the rest of the argument definition is +correct as early as possible (in essence, everything but the parsing of the argument name is done at +compile time). + +Everything that follows a `WithType()` call is thus type checked to only take `T` values. + +If this call is omitted, the parser generator assumes you are building a `Unit` type (i.e. an +argument that only cares about presence). + +#### WithRange method (optional) +Some values will not make sense outside of a `[min, max]` range, so this is an option to quickly add +a range check without writing custom code. The range check is performed after the main parsing +happens and happens for any type implementing the `<=` operators. + +#### WithValueMap (optional) +When parsing an enumeration, it might be very convenient to map a list of possible argument string +values into its runtime value. + +With something like +``` + .Define("-hello:_") + .WithValueMap({"world", kWorld}, + {"galaxy", kGalaxy}) +``` +It will parse either `-hello:world` or `-hello:galaxy` only (and error out on other variations of +`-hello:whatever`), converting it to the type-safe value of `kWorld` or `kGalaxy` respectively. + +This is meant to be another shorthand (like `WithRange`) to avoid writing a custom type parser. In +general it takes a variadic number of `pair`. + +#### WithValues (optional) +When an argument definition has multiple aliases with no wildcards, it might be convenient to +quickly map them into discrete values. + +For example: +``` + .Define({"-xinterpret", "-xnointerpret"}) + .WithValues({true, false} +``` +It will parse `-xinterpret` as `true` and `-xnointerpret` as `false`. + +In general, it uses the position of the argument alias to map into the WithValues position value. + +(Note that this method will not work when the argument definitions have a wildcard because there is +no way to position-ally match that). + +#### AppendValues (optional) +By default, the argument is assumed to appear exactly once, and if the user specifies it more than +once, only the latest value is taken into account (and all previous occurrences of the argument are +ignored). + +In some situations, we may want to accumulate the argument values instead of discarding the previous +ones. + +For example +``` + .Define("-D") + .WithType)() + .AppendValues() +``` +Will parse something like `-Dhello -Dworld -Dbar -Dbaz` into `std::vector{"hello", +"world", "bar", "baz"}`. + +### Setting an argument parse target (required) +To complete an argument definition, the parser generator also needs to know where to save values. +Currently, only `IntoKey` is supported, but that may change in the future. + +#### IntoKey (required) +This specifies that when a value is parsed, it will get saved into a variant map using the specific +key. + +For example, +``` + .Define("-help") + .IntoKey(Map::Help) +``` +will save occurrences of the `-help` argument by doing a `Map.Set(Map::Help, ParsedValue("-help"))` +where `ParsedValue` is an imaginary function that parses the `-help` argment into a specific type +set by `WithType`. + +### Ignoring unknown arguments +This is highly discouraged, but for compatibility with `JNI` which allows argument ignores, there is +an option to ignore any argument tokens that are not known to the parser. This is done with the +`Ignore` function which takes a list of argument definition names. + +It's semantically equivalent to making a series of argument definitions that map to `Unit` but don't +get saved anywhere. Values will still get parsed as normal, so it will *not* ignore known arguments +with invalid values, only user-arguments for which it could not find a matching argument definition. + +### Parsing custom types +Any type can be parsed from a string by specializing the `CmdlineType` class and implementing the +static interface provided by `CmdlineTypeParser`. It is recommended to inherit from +`CmdlineTypeParser` since it already provides default implementations for every method. + +The `Parse` method should be implemented for most types. Some types will allow appending (such as an +`std::vector` and are meant to be used with `AppendValues` in which case the +`ParseAndAppend` function should be implemented. + +For example: +``` +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& str) { + char* end = nullptr; + errno = 0; + double value = strtod(str.c_str(), &end); + + if (*end != '\0') { + return Result::Failure("Failed to parse double from " + str); + } + if (errno == ERANGE) { + return Result::OutOfRange( + "Failed to parse double from " + str + "; overflow/underflow occurred"); + } + + return Result::Success(value); + } + + static const char* Name() { return "double"; } + // note: Name() is just here for more user-friendly errors, + // but in the future we will use non-standard ways of getting the type name + // at compile-time and this will no longer be required +}; +``` +Will parse any non-append argument definitions with a type of `double`. + +For an appending example: +``` +template <> +struct CmdlineType> : CmdlineTypeParser> { + Result ParseAndAppend(const std::string& args, + std::vector& existing_value) { + existing_value.push_back(args); + return Result::SuccessNoValue(); + } + static const char* Name() { return "std::vector"; } +}; +``` +Will parse multiple instances of the same argument repeatedly into the `existing_value` (which will +be default-constructed to `T{}` for the first occurrence of the argument). + +#### What is a `Result`? +`Result` is a typedef for `CmdlineParseResult` and it acts similar to a poor version of +`Either` in Haskell. In particular, it would be similar to `Either< int ErrorCode, +Maybe >`. + +There are helpers like `Result::Success(value)`, `Result::Failure(string message)` and so on to +quickly construct these without caring about the type. + +When successfully parsing a single value, `Result::Success(value)` should be used, and when +successfully parsing an appended value, use `Result::SuccessNoValue()` and write back the new value +into `existing_value` as an out-parameter. + +When many arguments are parsed, the result is collapsed down to a `CmdlineResult` which acts as a +`Either` where the right side simply indicates success. When values are +successfully stored, the parser will automatically save it into the target destination as a side +effect. diff --git a/cmdline/cmdline_parse_result.h b/cmdline/cmdline_parse_result.h new file mode 100644 index 000000000..d6ac34166 --- /dev/null +++ b/cmdline/cmdline_parse_result.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_CMDLINE_PARSE_RESULT_H_ +#define ART_CMDLINE_CMDLINE_PARSE_RESULT_H_ + +#include "cmdline_result.h" +#include "detail/cmdline_parser_detail.h" + +namespace art { +// Result of a type-parsing attempt. If successful holds the strongly-typed value, +// otherwise it holds either a usage or a failure string message that should be displayed back +// to the user. +// +// CmdlineType::Parse/CmdlineType::ParseAndAppend must return this type. +template +struct CmdlineParseResult : CmdlineResult { + using CmdlineResult::CmdlineResult; + + // Create an error result with the usage error code and the specified message. + static CmdlineParseResult Usage(const std::string& message) { + return CmdlineParseResult(kUsage, message); + } + + // Create an error result with the failure error code and no message. + static CmdlineParseResult Failure() { + return CmdlineParseResult(kFailure); + } + + // Create an error result with the failure error code and no message. + static CmdlineParseResult Failure(const std::string& message) { + return CmdlineParseResult(kFailure, message); + } + + // Create a successful result which holds the specified value. + static CmdlineParseResult Success(const T& value) { + return CmdlineParseResult(value); + } + + // Create a successful result, taking over the value. + static CmdlineParseResult Success(T&& value) { + return CmdlineParseResult(std::forward(value)); + } + + // Create succesful result, without any values. Used when a value was successfully appended + // into an existing object. + static CmdlineParseResult SuccessNoValue() { + return CmdlineParseResult(T {}); + } + + // Create an error result with the OutOfRange error and the specified message. + static CmdlineParseResult OutOfRange(const std::string& message) { + return CmdlineParseResult(kOutOfRange, message); + } + + // Create an error result with the OutOfRange code and a custom message + // which is printed from the actual/min/max values. + // Values are converted to string using the ostream<< operator. + static CmdlineParseResult OutOfRange(const T& value, + const T& min, + const T& max) { + return CmdlineParseResult(kOutOfRange, + "actual: " + art::detail::ToStringAny(value) + + ", min: " + art::detail::ToStringAny(min) + + ", max: " + art::detail::ToStringAny(max)); + } + + // Get a read-only reference to the underlying value. + // The result must have been successful and must have a value. + const T& GetValue() const { + assert(IsSuccess()); + assert(has_value_); + return value_; + } + + // Get a mutable reference to the underlying value. + // The result must have been successful and must have a value. + T& GetValue() { + assert(IsSuccess()); + assert(has_value_); + return value_; + } + + // Take over the value. + // The result must have been successful and must have a value. + T&& ReleaseValue() { + assert(IsSuccess()); + assert(has_value_); + return std::move(value_); + } + + // Whether or not the result has a value (e.g. created with Result::Success). + // Error results never have values, success results commonly, but not always, have values. + bool HasValue() const { + return has_value_; + } + + // Cast an error-result from type T2 to T1. + // Safe since error-results don't store a typed value. + template + static CmdlineParseResult CastError(const CmdlineParseResult& other) { + assert(other.IsError()); + return CmdlineParseResult(other.GetStatus()); + } + + // Make sure copying is allowed + CmdlineParseResult(const CmdlineParseResult& other) = default; + // Make sure moving is cheap + CmdlineParseResult(CmdlineParseResult&& other) = default; + + private: + explicit CmdlineParseResult(const T& value) + : CmdlineResult(kSuccess), value_(value), has_value_(true) {} + explicit CmdlineParseResult(T&& value) + : CmdlineResult(kSuccess), value_(std::forward(value)), has_value_(true) {} + explicit CmdlineParseResult() + : CmdlineResult(kSuccess), value_(), has_value_(false) {} + + T value_; + bool has_value_ = false; +}; + +} // namespace art + +#endif // ART_CMDLINE_CMDLINE_PARSE_RESULT_H_ diff --git a/cmdline/cmdline_parser.h b/cmdline/cmdline_parser.h new file mode 100644 index 000000000..a55535639 --- /dev/null +++ b/cmdline/cmdline_parser.h @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_CMDLINE_PARSER_H_ +#define ART_CMDLINE_CMDLINE_PARSER_H_ + +#define CMDLINE_NDEBUG 1 // Do not output any debugging information for parsing. + +#include "cmdline/detail/cmdline_parser_detail.h" +#include "cmdline/detail/cmdline_parse_argument_detail.h" +#include "cmdline/detail/cmdline_debug_detail.h" + +#include "cmdline_type_parser.h" +#include "token_range.h" +#include "cmdline_types.h" +#include "cmdline_result.h" +#include "cmdline_parse_result.h" + +#include "runtime/base/variant_map.h" +#include "utils.h" + +#include +#include + +namespace art { +// Build a parser for command line arguments with a small domain specific language. +// Each parsed type must have a specialized CmdlineType in order to do the string->T parsing. +// Each argument must also have a VariantMap::Key in order to do the T storage. +template class TVariantMapKey> +struct CmdlineParser { + template + struct ArgumentBuilder; + + struct Builder; // Build the parser. + struct UntypedArgumentBuilder; // Build arguments which weren't yet given a type. + + private: + // Forward declare some functions that we need to use before fully-defining structs. + template + static ArgumentBuilder CreateArgumentBuilder(Builder& parent); + static void AppendCompletedArgument(Builder& builder, detail::CmdlineParseArgumentAny* arg); + + // Allow argument definitions to save their values when they are parsed, + // without having a dependency on CmdlineParser or any of the builders. + // + // A shared pointer to the save destination is saved into the load/save argument callbacks. + // + // This also allows the underlying storage (i.e. a variant map) to be released + // to the user, without having to recreate all of the callbacks. + struct SaveDestination { + SaveDestination() : variant_map_(new TVariantMap()) {} + + // Save value to the variant map. + template + void SaveToMap(const TVariantMapKey& key, TArg& value) { + variant_map_->Set(key, value); + } + + // Get the existing value from a map, creating the value if it did not already exist. + template + TArg& GetOrCreateFromMap(const TVariantMapKey& key) { + auto* ptr = variant_map_->Get(key); + if (ptr == nullptr) { + variant_map_->Set(key, TArg()); + ptr = variant_map_->Get(key); + assert(ptr != nullptr); + } + + return *ptr; + } + + protected: + // Release the map, clearing it as a side-effect. + // Future saves will be distinct from previous saves. + TVariantMap&& ReleaseMap() { + return std::move(*variant_map_); + } + + // Get a read-only reference to the variant map. + const TVariantMap& GetMap() { + return *variant_map_; + } + + // Clear all potential save targets. + void Clear() { + variant_map_->Clear(); + } + + private: + // Don't try to copy or move this. Just don't. + SaveDestination(const SaveDestination&) = delete; + SaveDestination(SaveDestination&&) = delete; + SaveDestination& operator=(const SaveDestination&) = delete; + SaveDestination& operator=(SaveDestination&&) = delete; + + std::shared_ptr variant_map_; + + // Allow the parser to change the underlying pointers when we release the underlying storage. + friend struct CmdlineParser; + }; + + public: + // Builder for the argument definition of type TArg. Do not use this type directly, + // it is only a separate type to provide compile-time enforcement against doing + // illegal builds. + template + struct ArgumentBuilder { + // Add a range check to this argument. + ArgumentBuilder& WithRange(const TArg& min, const TArg& max) { + argument_info_.has_range_ = true; + argument_info_.min_ = min; + argument_info_.max_ = max; + + return *this; + } + + // Map the list of names into the list of values. List of names must not have + // any wildcards '_' in it. + // + // Do not use if a value map has already been set. + ArgumentBuilder& WithValues(std::initializer_list value_list) { + SetValuesInternal(value_list); + return *this; + } + + // When used with a single alias, map the alias into this value. + // Same as 'WithValues({value})' , but allows the omission of the curly braces {}. + ArgumentBuilder WithValue(const TArg& value) { + return WithValues({ value }); + } + + // Map the parsed string values (from _) onto a concrete value. If no wildcard + // has been specified, then map the value directly from the arg name (i.e. + // if there are multiple aliases, then use the alias to do the mapping). + // + // Do not use if a values list has already been set. + ArgumentBuilder& WithValueMap( + std::initializer_list> key_value_list) { + assert(!argument_info_.has_value_list_); + + argument_info_.has_value_map_ = true; + argument_info_.value_map_ = key_value_list; + + return *this; + } + + // If this argument is seen multiple times, successive arguments mutate the same value + // instead of replacing it with a new value. + ArgumentBuilder& AppendValues() { + argument_info_.appending_values_ = true; + + return *this; + } + + // Convenience type alias for the variant map key type definition. + using MapKey = TVariantMapKey; + + // Write the results of this argument into the key. + // To look up the parsed arguments, get the map and then use this key with VariantMap::Get + CmdlineParser::Builder& IntoKey(const MapKey& key) { + // Only capture save destination as a pointer. + // This allows the parser to later on change the specific save targets. + auto save_destination = save_destination_; + save_value_ = [save_destination, &key](TArg& value) { + save_destination->SaveToMap(key, value); + CMDLINE_DEBUG_LOG << "Saved value into map '" + << detail::ToStringAny(value) << "'" << std::endl; + }; + + load_value_ = [save_destination, &key]() -> TArg& { + TArg& value = save_destination->GetOrCreateFromMap(key); + CMDLINE_DEBUG_LOG << "Loaded value from map '" << detail::ToStringAny(value) << "'" + << std::endl; + + return value; + }; + + save_value_specified_ = true; + load_value_specified_ = true; + + CompleteArgument(); + return parent_; + } + + // Ensure we always move this when returning a new builder. + ArgumentBuilder(ArgumentBuilder&&) = default; + + protected: + // Used by builder to internally ignore arguments by dropping them on the floor after parsing. + CmdlineParser::Builder& IntoIgnore() { + save_value_ = [](TArg& value) { + CMDLINE_DEBUG_LOG << "Ignored value '" << detail::ToStringAny(value) << "'" << std::endl; + }; + load_value_ = []() -> TArg& { + assert(false && "Should not be appending values to ignored arguments"); + return *reinterpret_cast(0); // Blow up. + }; + + save_value_specified_ = true; + load_value_specified_ = true; + + CompleteArgument(); + return parent_; + } + + void SetValuesInternal(const std::vector&& value_list) { + assert(!argument_info_.has_value_map_); + + argument_info_.has_value_list_ = true; + argument_info_.value_list_ = value_list; + } + + void SetNames(std::vector&& names) { + argument_info_.names_ = names; + } + + void SetNames(std::initializer_list names) { + argument_info_.names_ = names; + } + + private: + // Copying is bad. Move only. + ArgumentBuilder(const ArgumentBuilder&) = delete; + + // Called by any function that doesn't chain back into this builder. + // Completes the argument builder and save the information into the main builder. + void CompleteArgument() { + assert(save_value_specified_ && + "No Into... function called, nowhere to save parsed values to"); + assert(load_value_specified_ && + "No Into... function called, nowhere to load parsed values from"); + + argument_info_.CompleteArgument(); + + // Appending the completed argument is destructive. The object is no longer + // usable since all the useful information got moved out of it. + AppendCompletedArgument(parent_, + new detail::CmdlineParseArgument( + std::move(argument_info_), + std::move(save_value_), + std::move(load_value_))); + } + + friend struct CmdlineParser; + friend struct CmdlineParser::Builder; + friend struct CmdlineParser::UntypedArgumentBuilder; + + ArgumentBuilder(CmdlineParser::Builder& parser, + std::shared_ptr save_destination) + : parent_(parser), + save_value_specified_(false), + load_value_specified_(false), + save_destination_(save_destination) { + save_value_ = [](TArg&) { + assert(false && "No save value function defined"); + }; + + load_value_ = []() -> TArg& { + assert(false && "No load value function defined"); + return *reinterpret_cast(0); // Blow up. + }; + } + + CmdlineParser::Builder& parent_; + std::function save_value_; + std::function load_value_; + bool save_value_specified_; + bool load_value_specified_; + detail::CmdlineParserArgumentInfo argument_info_; + + std::shared_ptr save_destination_; + }; + + struct UntypedArgumentBuilder { + // Set a type for this argument. The specific subcommand parser is looked up by the type. + template + ArgumentBuilder WithType() { + return CreateTypedBuilder(); + } + + // When used with multiple aliases, map the position of the alias to the value position. + template + ArgumentBuilder WithValues(std::initializer_list values) { + auto&& a = CreateTypedBuilder(); + a.WithValues(values); + return std::move(a); + } + + // When used with a single alias, map the alias into this value. + // Same as 'WithValues({value})' , but allows the omission of the curly braces {}. + template + ArgumentBuilder WithValue(const TArg& value) { + return WithValues({ value }); + } + + // Set the current building argument to target this key. + // When this command line argument is parsed, it can be fetched with this key. + Builder& IntoKey(const TVariantMapKey& key) { + return CreateTypedBuilder().IntoKey(key); + } + + // Ensure we always move this when returning a new builder. + UntypedArgumentBuilder(UntypedArgumentBuilder&&) = default; + + protected: + void SetNames(std::vector&& names) { + names_ = std::move(names); + } + + void SetNames(std::initializer_list names) { + names_ = names; + } + + private: + // No copying. Move instead. + UntypedArgumentBuilder(const UntypedArgumentBuilder&) = delete; + + template + ArgumentBuilder CreateTypedBuilder() { + auto&& b = CreateArgumentBuilder(parent_); + InitializeTypedBuilder(&b); // Type-specific initialization + b.SetNames(std::move(names_)); + return std::move(b); + } + + template + typename std::enable_if::value>::type + InitializeTypedBuilder(ArgumentBuilder* arg_builder) { + // Every Unit argument implicitly maps to a runtime value of Unit{} + std::vector values(names_.size(), Unit{}); // NOLINT [whitespace/braces] [5] + arg_builder->SetValuesInternal(std::move(values)); + } + + // No extra work for all other types + void InitializeTypedBuilder(void*) {} + + template + friend struct ArgumentBuilder; + friend struct Builder; + + explicit UntypedArgumentBuilder(CmdlineParser::Builder& parent) : parent_(parent) {} + // UntypedArgumentBuilder(UntypedArgumentBuilder&& other) = default; + + CmdlineParser::Builder& parent_; + std::vector names_; + }; + + // Build a new parser given a chain of calls to define arguments. + struct Builder { + Builder() : save_destination_(new SaveDestination()) {} + + // Define a single argument. The default type is Unit. + UntypedArgumentBuilder Define(const char* name) { + return Define({name}); + } + + // Define a single argument with multiple aliases. + UntypedArgumentBuilder Define(std::initializer_list names) { + auto&& b = UntypedArgumentBuilder(*this); + b.SetNames(names); + return std::move(b); + } + + // Whether the parser should give up on unrecognized arguments. Not recommended. + Builder& IgnoreUnrecognized(bool ignore_unrecognized) { + ignore_unrecognized_ = ignore_unrecognized; + return *this; + } + + // Provide a list of arguments to ignore for backwards compatibility. + Builder& Ignore(std::initializer_list ignore_list) { + for (auto&& ignore_name : ignore_list) { + std::string ign = ignore_name; + + // Ignored arguments are just like a regular definition which have very + // liberal parsing requirements (no range checks, no value checks). + // Unlike regular argument definitions, when a value gets parsed into its + // stronger type, we just throw it away. + + if (ign.find("_") != std::string::npos) { // Does the arg-def have a wildcard? + // pretend this is a string, e.g. -Xjitconfig: + auto&& builder = Define(ignore_name).template WithType().IntoIgnore(); + assert(&builder == this); + (void)builder; // Ignore pointless unused warning, it's used in the assert. + } else { + // pretend this is a unit, e.g. -Xjitblocking + auto&& builder = Define(ignore_name).template WithType().IntoIgnore(); + assert(&builder == this); + (void)builder; // Ignore pointless unused warning, it's used in the assert. + } + } + ignore_list_ = ignore_list; + return *this; + } + + // Finish building the parser; performs sanity checks. Return value is moved, not copied. + // Do not call this more than once. + CmdlineParser Build() { + assert(!built_); + built_ = true; + + auto&& p = CmdlineParser(ignore_unrecognized_, + std::move(ignore_list_), + save_destination_, + std::move(completed_arguments_)); + + return std::move(p); + } + + protected: + void AppendCompletedArgument(detail::CmdlineParseArgumentAny* arg) { + auto smart_ptr = std::unique_ptr(arg); + completed_arguments_.push_back(std::move(smart_ptr)); + } + + private: + // No copying now! + Builder(const Builder& other) = delete; + + template + friend struct ArgumentBuilder; + friend struct UntypedArgumentBuilder; + friend struct CmdlineParser; + + bool built_ = false; + bool ignore_unrecognized_ = false; + std::vector ignore_list_; + std::shared_ptr save_destination_; + + std::vector> completed_arguments_; + }; + + CmdlineResult Parse(const std::string& argv) { + std::vector tokenized; + Split(argv, ' ', &tokenized); + + return Parse(TokenRange(std::move(tokenized))); + } + + // Parse the arguments; storing results into the arguments map. Returns success value. + CmdlineResult Parse(const char* argv) { + return Parse(std::string(argv)); + } + + // Parse the arguments; storing the results into the arguments map. Returns success value. + // Assumes that argv[0] is a valid argument (i.e. not the program name). + CmdlineResult Parse(const std::vector& argv) { + return Parse(TokenRange(argv.begin(), argv.end())); + } + + // Parse the arguments; storing the results into the arguments map. Returns success value. + // Assumes that argv[0] is a valid argument (i.e. not the program name). + CmdlineResult Parse(const std::vector& argv) { + return Parse(TokenRange(argv.begin(), argv.end())); + } + + // Parse the arguments (directly from an int main(argv,argc)). Returns success value. + // Assumes that argv[0] is the program name, and ignores it. + CmdlineResult Parse(const char* argv[], int argc) { + return Parse(TokenRange(&argv[1], argc - 1)); // ignore argv[0] because it's the program name + } + + // Look up the arguments that have been parsed; use the target keys to lookup individual args. + const TVariantMap& GetArgumentsMap() const { + return save_destination_->GetMap(); + } + + // Release the arguments map that has been parsed; useful for move semantics. + TVariantMap&& ReleaseArgumentsMap() { + return save_destination_->ReleaseMap(); + } + + // How many arguments were defined? + size_t CountDefinedArguments() const { + return completed_arguments_.size(); + } + + // Ensure we have a default move constructor. + CmdlineParser(CmdlineParser&& other) = default; + // Ensure we have a default move assignment operator. + CmdlineParser& operator=(CmdlineParser&& other) = default; + + private: + friend struct Builder; + + // Construct a new parser from the builder. Move all the arguments. + explicit CmdlineParser(bool ignore_unrecognized, + std::vector&& ignore_list, + std::shared_ptr save_destination, + std::vector>&& + completed_arguments) + : ignore_unrecognized_(ignore_unrecognized), + ignore_list_(std::move(ignore_list)), + save_destination_(save_destination), + completed_arguments_(std::move(completed_arguments)) { + assert(save_destination != nullptr); + } + + // Parse the arguments; storing results into the arguments map. Returns success value. + // The parsing will fail on the first non-success parse result and return that error. + // + // All previously-parsed arguments are cleared out. + // Otherwise, all parsed arguments will be stored into SaveDestination as a side-effect. + // A partial parse will result only in a partial save of the arguments. + CmdlineResult Parse(TokenRange&& arguments_list) { + save_destination_->Clear(); + + for (size_t i = 0; i < arguments_list.Size(); ) { + TokenRange possible_name = arguments_list.Slice(i); + + size_t best_match_size = 0; // How many tokens were matched in the best case. + size_t best_match_arg_idx = 0; + bool matched = false; // At least one argument definition has been matched? + + // Find the closest argument definition for the remaining token range. + size_t arg_idx = 0; + for (auto&& arg : completed_arguments_) { + size_t local_match = arg->MaybeMatches(possible_name); + + if (local_match > best_match_size) { + best_match_size = local_match; + best_match_arg_idx = arg_idx; + matched = true; + } + arg_idx++; + } + + // Saw some kind of unknown argument + if (matched == false) { + if (UNLIKELY(ignore_unrecognized_)) { // This is usually off, we only need it for JNI. + // Consume 1 token and keep going, hopefully the next token is a good one. + ++i; + continue; + } + // Common case: + // Bail out on the first unknown argument with an error. + return CmdlineResult(CmdlineResult::kUnknown, + std::string("Unknown argument: ") + possible_name[0]); + } + + // Look at the best-matched argument definition and try to parse against that. + auto&& arg = completed_arguments_[best_match_arg_idx]; + + assert(arg->MaybeMatches(possible_name) == best_match_size); + + // Try to parse the argument now, if we have enough tokens. + std::pair num_tokens = arg->GetNumTokens(); + size_t min_tokens; + size_t max_tokens; + + std::tie(min_tokens, max_tokens) = num_tokens; + + if ((i + min_tokens) > arguments_list.Size()) { + // expected longer command line but it was too short + // e.g. if the argv was only "-Xms" without specifying a memory option + CMDLINE_DEBUG_LOG << "Parse failure, i = " << i << ", arg list " << arguments_list.Size() << + " num tokens in arg_def: " << min_tokens << "," << max_tokens << std::endl; + return CmdlineResult(CmdlineResult::kFailure, + std::string("Argument ") + + possible_name[0] + ": incomplete command line arguments, expected " + + std::to_string(size_t(i + min_tokens) - arguments_list.Size()) + + " more tokens"); + } + + if (best_match_size > max_tokens || best_match_size < min_tokens) { + // Even our best match was out of range, so parsing would fail instantly. + return CmdlineResult(CmdlineResult::kFailure, + std::string("Argument ") + possible_name[0] + ": too few tokens " + "matched " + std::to_string(best_match_size) + + " but wanted " + std::to_string(num_tokens.first)); + } + + // We have enough tokens to begin exact parsing. + TokenRange exact_range = possible_name.Slice(0, max_tokens); + + size_t consumed_tokens = 1; // At least 1 if we ever want to try to resume parsing on error + CmdlineResult parse_attempt = arg->ParseArgument(exact_range, &consumed_tokens); + + if (parse_attempt.IsError()) { + // We may also want to continue parsing the other tokens to gather more errors. + return parse_attempt; + } // else the value has been successfully stored into the map + + assert(consumed_tokens > 0); // Don't hang in an infinite loop trying to parse + i += consumed_tokens; + + // TODO: also handle ignoring arguments for backwards compatibility + } // for + + return CmdlineResult(CmdlineResult::kSuccess); + } + + bool ignore_unrecognized_ = false; + std::vector ignore_list_; + std::shared_ptr save_destination_; + std::vector> completed_arguments_; +}; + +// This has to be defined after everything else, since we want the builders to call this. +template class TVariantMapKey> +template +CmdlineParser::ArgumentBuilder +CmdlineParser::CreateArgumentBuilder( + CmdlineParser::Builder& parent) { + return CmdlineParser::ArgumentBuilder( + parent, parent.save_destination_); +} + +// This has to be defined after everything else, since we want the builders to call this. +template class TVariantMapKey> +void CmdlineParser::AppendCompletedArgument( + CmdlineParser::Builder& builder, + detail::CmdlineParseArgumentAny* arg) { + builder.AppendCompletedArgument(arg); +} + +} // namespace art + +#endif // ART_CMDLINE_CMDLINE_PARSER_H_ diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc new file mode 100644 index 000000000..a875641a4 --- /dev/null +++ b/cmdline/cmdline_parser_test.cc @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cmdline_parser.h" +#include "runtime/runtime_options.h" +#include "runtime/parsed_options.h" + +#include "utils.h" +#include +#include "gtest/gtest.h" + +#define EXPECT_NULL(expected) EXPECT_EQ(reinterpret_cast(expected), \ + reinterpret_cast(NULL)); + +namespace art { + bool UsuallyEquals(double expected, double actual); + + // This has a gtest dependency, which is why it's in the gtest only. + bool operator==(const TestProfilerOptions& lhs, const TestProfilerOptions& rhs) { + return lhs.enabled_ == rhs.enabled_ && + lhs.output_file_name_ == rhs.output_file_name_ && + lhs.period_s_ == rhs.period_s_ && + lhs.duration_s_ == rhs.duration_s_ && + lhs.interval_us_ == rhs.interval_us_ && + UsuallyEquals(lhs.backoff_coefficient_, rhs.backoff_coefficient_) && + UsuallyEquals(lhs.start_immediately_, rhs.start_immediately_) && + UsuallyEquals(lhs.top_k_threshold_, rhs.top_k_threshold_) && + UsuallyEquals(lhs.top_k_change_threshold_, rhs.top_k_change_threshold_) && + lhs.profile_type_ == rhs.profile_type_ && + lhs.max_stack_depth_ == rhs.max_stack_depth_; + } + + bool UsuallyEquals(double expected, double actual) { + using FloatingPoint = ::testing::internal::FloatingPoint; + + FloatingPoint exp(expected); + FloatingPoint act(actual); + + // Compare with ULPs instead of comparing with == + return exp.AlmostEquals(act); + } + + template + bool UsuallyEquals(const T& expected, const T& actual, + typename std::enable_if< + detail::SupportsEqualityOperator::value>::type* = 0) { + return expected == actual; + } + + // Try to use memcmp to compare simple plain-old-data structs. + // + // This should *not* generate false positives, but it can generate false negatives. + // This will mostly work except for fields like float which can have different bit patterns + // that are nevertheless equal. + // If a test is failing because the structs aren't "equal" when they really are + // then it's recommended to implement operator== for it instead. + template + bool UsuallyEquals(const T& expected, const T& actual, + const Ignore& ... more ATTRIBUTE_UNUSED, + typename std::enable_if::value>::type* = 0, + typename std::enable_if::value>::type* = 0 + ) { + return memcmp(std::addressof(expected), std::addressof(actual), sizeof(T)) == 0; + } + + bool UsuallyEquals(const XGcOption& expected, const XGcOption& actual) { + return memcmp(std::addressof(expected), std::addressof(actual), sizeof(expected)) == 0; + } + + bool UsuallyEquals(const char* expected, std::string actual) { + return std::string(expected) == actual; + } + + template + ::testing::AssertionResult IsExpectedKeyValue(const T& expected, + const TMap& map, + const TKey& key) { + auto* actual = map.Get(key); + if (actual != nullptr) { + if (!UsuallyEquals(expected, *actual)) { + return ::testing::AssertionFailure() + << "expected " << detail::ToStringAny(expected) << " but got " + << detail::ToStringAny(*actual); + } + return ::testing::AssertionSuccess(); + } + + return ::testing::AssertionFailure() << "key was not in the map"; + } + +class CmdlineParserTest : public ::testing::Test { + public: + CmdlineParserTest() = default; + ~CmdlineParserTest() = default; + + protected: + using M = RuntimeArgumentMap; + using RuntimeParser = ParsedOptions::RuntimeParser; + + static void SetUpTestCase() { + art::InitLogging(nullptr); // argv = null + } + + virtual void SetUp() { + parser_ = ParsedOptions::MakeParser(false); // do not ignore unrecognized options + } + + static ::testing::AssertionResult IsResultSuccessful(CmdlineResult result) { + if (result.IsSuccess()) { + return ::testing::AssertionSuccess(); + } else { + return ::testing::AssertionFailure() + << result.GetStatus() << " with: " << result.GetMessage(); + } + } + + static ::testing::AssertionResult IsResultFailure(CmdlineResult result, + CmdlineResult::Status failure_status) { + if (result.IsSuccess()) { + return ::testing::AssertionFailure() << " got success but expected failure: " + << failure_status; + } else if (result.GetStatus() == failure_status) { + return ::testing::AssertionSuccess(); + } + + return ::testing::AssertionFailure() << " expected failure " << failure_status + << " but got " << result.GetStatus(); + } + + std::unique_ptr parser_; +}; + +#define EXPECT_KEY_EXISTS(map, key) EXPECT_TRUE((map).Exists(key)) +#define EXPECT_KEY_VALUE(map, key, expected) EXPECT_TRUE(IsExpectedKeyValue(expected, map, key)) + +#define EXPECT_SINGLE_PARSE_EMPTY_SUCCESS(argv) \ + do { \ + EXPECT_TRUE(IsResultSuccessful(parser_->Parse(argv))); \ + EXPECT_EQ(0u, parser_->GetArgumentsMap().Size()); \ + } while (false) + +#define _EXPECT_SINGLE_PARSE_EXISTS(argv, key) \ + do { \ + EXPECT_TRUE(IsResultSuccessful(parser_->Parse(argv))); \ + RuntimeArgumentMap args = parser_->ReleaseArgumentsMap(); \ + EXPECT_EQ(1u, args.Size()); \ + EXPECT_KEY_EXISTS(args, key); \ + +#define EXPECT_SINGLE_PARSE_EXISTS(argv, key) \ + _EXPECT_SINGLE_PARSE_EXISTS(argv, key); \ + } while (false) + +#define EXPECT_SINGLE_PARSE_VALUE(expected, argv, key) \ + _EXPECT_SINGLE_PARSE_EXISTS(argv, key); \ + EXPECT_KEY_VALUE(args, key, expected); \ + } while (false) // NOLINT [readability/namespace] [5] + +#define EXPECT_SINGLE_PARSE_VALUE_STR(expected, argv, key) \ + EXPECT_SINGLE_PARSE_VALUE(std::string(expected), argv, key) + +#define EXPECT_SINGLE_PARSE_FAIL(argv, failure_status) \ + do { \ + EXPECT_TRUE(IsResultFailure(parser_->Parse(argv), failure_status));\ + RuntimeArgumentMap args = parser_->ReleaseArgumentsMap();\ + EXPECT_EQ(0u, args.Size()); \ + } while (false) + +TEST_F(CmdlineParserTest, TestSimpleSuccesses) { + auto& parser = *parser_; + + EXPECT_LT(0u, parser.CountDefinedArguments()); + + { + // Test case 1: No command line arguments + EXPECT_TRUE(IsResultSuccessful(parser.Parse(""))); + RuntimeArgumentMap args = parser.ReleaseArgumentsMap(); + EXPECT_EQ(0u, args.Size()); + } + + EXPECT_SINGLE_PARSE_EXISTS("-Xzygote", M::Zygote); + EXPECT_SINGLE_PARSE_VALUE_STR("/hello/world", "-Xbootclasspath:/hello/world", M::BootClassPath); + EXPECT_SINGLE_PARSE_VALUE("/hello/world", "-Xbootclasspath:/hello/world", M::BootClassPath); + EXPECT_SINGLE_PARSE_VALUE(false, "-Xverify:none", M::Verify); + EXPECT_SINGLE_PARSE_VALUE(true, "-Xverify:remote", M::Verify); + EXPECT_SINGLE_PARSE_VALUE(true, "-Xverify:all", M::Verify); + EXPECT_SINGLE_PARSE_VALUE(Memory<1>(234), "-Xss234", M::StackSize); + EXPECT_SINGLE_PARSE_VALUE(MemoryKiB(1234*MB), "-Xms1234m", M::MemoryInitialSize); + EXPECT_SINGLE_PARSE_VALUE(true, "-XX:EnableHSpaceCompactForOOM", M::EnableHSpaceCompactForOOM); + EXPECT_SINGLE_PARSE_VALUE(false, "-XX:DisableHSpaceCompactForOOM", M::EnableHSpaceCompactForOOM); + EXPECT_SINGLE_PARSE_VALUE(0.5, "-XX:HeapTargetUtilization=0.5", M::HeapTargetUtilization); + EXPECT_SINGLE_PARSE_VALUE(5u, "-XX:ParallelGCThreads=5", M::ParallelGCThreads); +} // TEST_F + +TEST_F(CmdlineParserTest, TestSimpleFailures) { + // Test argument is unknown to the parser + EXPECT_SINGLE_PARSE_FAIL("abcdefg^%@#*(@#", CmdlineResult::kUnknown); + // Test value map substitution fails + EXPECT_SINGLE_PARSE_FAIL("-Xverify:whatever", CmdlineResult::kFailure); + // Test value type parsing failures + EXPECT_SINGLE_PARSE_FAIL("-Xsswhatever", CmdlineResult::kFailure); // invalid memory value + EXPECT_SINGLE_PARSE_FAIL("-Xms123", CmdlineResult::kFailure); // memory value too small + EXPECT_SINGLE_PARSE_FAIL("-XX:HeapTargetUtilization=0.0", CmdlineResult::kOutOfRange); // toosmal + EXPECT_SINGLE_PARSE_FAIL("-XX:HeapTargetUtilization=2.0", CmdlineResult::kOutOfRange); // toolarg + EXPECT_SINGLE_PARSE_FAIL("-XX:ParallelGCThreads=-5", CmdlineResult::kOutOfRange); // too small + EXPECT_SINGLE_PARSE_FAIL("-Xgc:blablabla", CmdlineResult::kUsage); // not a valid suboption +} // TEST_F + +TEST_F(CmdlineParserTest, TestLogVerbosity) { + { + const char* log_args = "-verbose:" + "class,compiler,gc,heap,jdwp,jni,monitor,profiler,signals,startup,third-party-jni," + "threads,verifier"; + + LogVerbosity log_verbosity = LogVerbosity(); + log_verbosity.class_linker = true; + log_verbosity.compiler = true; + log_verbosity.gc = true; + log_verbosity.heap = true; + log_verbosity.jdwp = true; + log_verbosity.jni = true; + log_verbosity.monitor = true; + log_verbosity.profiler = true; + log_verbosity.signals = true; + log_verbosity.startup = true; + log_verbosity.third_party_jni = true; + log_verbosity.threads = true; + log_verbosity.verifier = true; + + EXPECT_SINGLE_PARSE_VALUE(log_verbosity, log_args, M::Verbose); + } + + { + const char* log_args = "-verbose:" + "class,compiler,gc,heap,jdwp,jni,monitor"; + + LogVerbosity log_verbosity = LogVerbosity(); + log_verbosity.class_linker = true; + log_verbosity.compiler = true; + log_verbosity.gc = true; + log_verbosity.heap = true; + log_verbosity.jdwp = true; + log_verbosity.jni = true; + log_verbosity.monitor = true; + + EXPECT_SINGLE_PARSE_VALUE(log_verbosity, log_args, M::Verbose); + } + + EXPECT_SINGLE_PARSE_FAIL("-verbose:blablabla", CmdlineResult::kUsage); // invalid verbose opt +} // TEST_F + +TEST_F(CmdlineParserTest, TestXGcOption) { + /* + * Test success + */ + { + XGcOption option_all_true{}; // NOLINT [readability/braces] [4] + option_all_true.collector_type_ = gc::CollectorType::kCollectorTypeCMS; + option_all_true.verify_pre_gc_heap_ = true; + option_all_true.verify_pre_sweeping_heap_ = true; + option_all_true.verify_post_gc_heap_ = true; + option_all_true.verify_pre_gc_rosalloc_ = true; + option_all_true.verify_pre_sweeping_rosalloc_ = true; + option_all_true.verify_post_gc_rosalloc_ = true; + + const char * xgc_args_all_true = "-Xgc:concurrent," + "preverify,presweepingverify,postverify," + "preverify_rosalloc,presweepingverify_rosalloc," + "postverify_rosalloc,precise," + "verifycardtable"; + + EXPECT_SINGLE_PARSE_VALUE(option_all_true, xgc_args_all_true, M::GcOption); + + XGcOption option_all_false{}; // NOLINT [readability/braces] [4] + option_all_false.collector_type_ = gc::CollectorType::kCollectorTypeMS; + option_all_false.verify_pre_gc_heap_ = false; + option_all_false.verify_pre_sweeping_heap_ = false; + option_all_false.verify_post_gc_heap_ = false; + option_all_false.verify_pre_gc_rosalloc_ = false; + option_all_false.verify_pre_sweeping_rosalloc_ = false; + option_all_false.verify_post_gc_rosalloc_ = false; + + const char* xgc_args_all_false = "-Xgc:nonconcurrent," + "nopreverify,nopresweepingverify,nopostverify,nopreverify_rosalloc," + "nopresweepingverify_rosalloc,nopostverify_rosalloc,noprecise,noverifycardtable"; + + EXPECT_SINGLE_PARSE_VALUE(option_all_false, xgc_args_all_false, M::GcOption); + + XGcOption option_all_default{}; // NOLINT [readability/braces] [4] + + option_all_default.collector_type_ = gc::kCollectorTypeDefault; + option_all_default.verify_pre_gc_heap_ = false; + option_all_default.verify_pre_sweeping_heap_ = kIsDebugBuild; + option_all_default.verify_post_gc_heap_ = false; + option_all_default.verify_pre_gc_rosalloc_ = kIsDebugBuild; + option_all_default.verify_pre_sweeping_rosalloc_ = false; + option_all_default.verify_post_gc_rosalloc_ = false; + + const char* xgc_args_blank = "-Xgc:"; + EXPECT_SINGLE_PARSE_VALUE(option_all_default, xgc_args_blank, M::GcOption); + } + + /* + * Test failures + */ + EXPECT_SINGLE_PARSE_FAIL("-Xgc:blablabla", CmdlineResult::kUsage); // invalid Xgc opt +} // TEST_F + +/* + * {"-Xrunjdwp:_", "-agentlib:jdwp=_"} + */ +TEST_F(CmdlineParserTest, TestJdwpOptions) { + /* + * Test success + */ + { + /* + * "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n" + */ + JDWP::JdwpOptions opt = JDWP::JdwpOptions(); + opt.transport = JDWP::JdwpTransportType::kJdwpTransportSocket; + opt.port = 8000; + opt.server = true; + + const char *opt_args = "-Xrunjdwp:transport=dt_socket,address=8000,server=y"; + + EXPECT_SINGLE_PARSE_VALUE(opt, opt_args, M::JdwpOptions); + } + + { + /* + * "Example: -agentlib:jdwp=transport=dt_socket,address=localhost:6500,server=n\n"); + */ + JDWP::JdwpOptions opt = JDWP::JdwpOptions(); + opt.transport = JDWP::JdwpTransportType::kJdwpTransportSocket; + opt.host = "localhost"; + opt.port = 6500; + opt.server = false; + + const char *opt_args = "-agentlib:jdwp=transport=dt_socket,address=localhost:6500,server=n"; + + EXPECT_SINGLE_PARSE_VALUE(opt, opt_args, M::JdwpOptions); + } + + /* + * Test failures + */ + EXPECT_SINGLE_PARSE_FAIL("-Xrunjdwp:help", CmdlineResult::kUsage); // usage for help only + EXPECT_SINGLE_PARSE_FAIL("-Xrunjdwp:blabla", CmdlineResult::kFailure); // invalid subarg + EXPECT_SINGLE_PARSE_FAIL("-agentlib:jdwp=help", CmdlineResult::kUsage); // usage for help only + EXPECT_SINGLE_PARSE_FAIL("-agentlib:jdwp=blabla", CmdlineResult::kFailure); // invalid subarg +} // TEST_F + +/* + * -D_ -D_ -D_ ... + */ +TEST_F(CmdlineParserTest, TestPropertiesList) { + /* + * Test successes + */ + { + std::vector opt = {"hello"}; + + EXPECT_SINGLE_PARSE_VALUE(opt, "-Dhello", M::PropertiesList); + } + + { + std::vector opt = {"hello", "world"}; + + EXPECT_SINGLE_PARSE_VALUE(opt, "-Dhello -Dworld", M::PropertiesList); + } + + { + std::vector opt = {"one", "two", "three"}; + + EXPECT_SINGLE_PARSE_VALUE(opt, "-Done -Dtwo -Dthree", M::PropertiesList); + } +} // TEST_F + +/* +* -Xcompiler-option foo -Xcompiler-option bar ... +*/ +TEST_F(CmdlineParserTest, TestCompilerOption) { + /* + * Test successes + */ + { + std::vector opt = {"hello"}; + EXPECT_SINGLE_PARSE_VALUE(opt, "-Xcompiler-option hello", M::CompilerOptions); + } + + { + std::vector opt = {"hello", "world"}; + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xcompiler-option hello -Xcompiler-option world", + M::CompilerOptions); + } + + { + std::vector opt = {"one", "two", "three"}; + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xcompiler-option one -Xcompiler-option two -Xcompiler-option three", + M::CompilerOptions); + } +} // TEST_F + +/* +* -X-profile-* +*/ +TEST_F(CmdlineParserTest, TestProfilerOptions) { + /* + * Test successes + */ + + { + TestProfilerOptions opt; + opt.enabled_ = true; + + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xenable-profiler", + M::ProfilerOpts); + } + + { + TestProfilerOptions opt; + // also need to test 'enabled' + opt.output_file_name_ = "hello_world.txt"; + + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xprofile-filename:hello_world.txt ", + M::ProfilerOpts); + } + + { + TestProfilerOptions opt = TestProfilerOptions(); + // also need to test 'enabled' + opt.output_file_name_ = "output.txt"; + opt.period_s_ = 123u; + opt.duration_s_ = 456u; + opt.interval_us_ = 789u; + opt.backoff_coefficient_ = 2.0; + opt.start_immediately_ = true; + opt.top_k_threshold_ = 50.0; + opt.top_k_change_threshold_ = 60.0; + opt.profile_type_ = kProfilerMethod; + opt.max_stack_depth_ = 1337u; + + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xprofile-filename:output.txt " + "-Xprofile-period:123 " + "-Xprofile-duration:456 " + "-Xprofile-interval:789 " + "-Xprofile-backoff:2.0 " + "-Xprofile-start-immediately " + "-Xprofile-top-k-threshold:50.0 " + "-Xprofile-top-k-change-threshold:60.0 " + "-Xprofile-type:method " + "-Xprofile-max-stack-depth:1337", + M::ProfilerOpts); + } + + { + TestProfilerOptions opt = TestProfilerOptions(); + opt.profile_type_ = kProfilerBoundedStack; + + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xprofile-type:stack", + M::ProfilerOpts); + } +} // TEST_F + +TEST_F(CmdlineParserTest, TestIgnoreUnrecognized) { + RuntimeParser::Builder parserBuilder; + + parserBuilder + .Define("-help") + .IntoKey(M::Help) + .IgnoreUnrecognized(true); + + parser_.reset(new RuntimeParser(parserBuilder.Build())); + + EXPECT_SINGLE_PARSE_EMPTY_SUCCESS("-non-existent-option"); + EXPECT_SINGLE_PARSE_EMPTY_SUCCESS("-non-existent-option1 --non-existent-option-2"); +} // TEST_F + +TEST_F(CmdlineParserTest, TestIgnoredArguments) { + std::initializer_list ignored_args = { + "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", + "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:abdef", + "-Xdexopt:foobar", "-Xnoquithandler", "-Xjnigreflimit:ixnay", "-Xgenregmap", "-Xnogenregmap", + "-Xverifyopt:never", "-Xcheckdexsum", "-Xincludeselectedop", "-Xjitop:noop", + "-Xincludeselectedmethod", "-Xjitthreshold:123", "-Xjitcodecachesize:12345", + "-Xjitblocking", "-Xjitmethod:_", "-Xjitclass:nosuchluck", "-Xjitoffset:none", + "-Xjitconfig:yes", "-Xjitcheckcg", "-Xjitverbose", "-Xjitprofile", + "-Xjitdisableopt", "-Xjitsuspendpoll", "-XX:mainThreadStackSize=1337" + }; + + // Check they are ignored when parsed one at a time + for (auto&& arg : ignored_args) { + SCOPED_TRACE(arg); + EXPECT_SINGLE_PARSE_EMPTY_SUCCESS(arg); + } + + // Check they are ignored when we pass it all together at once + std::vector argv = ignored_args; + EXPECT_SINGLE_PARSE_EMPTY_SUCCESS(argv); +} // TEST_F + +TEST_F(CmdlineParserTest, MultipleArguments) { + EXPECT_TRUE(IsResultSuccessful(parser_->Parse( + "-help -XX:ForegroundHeapGrowthMultiplier=0.5 " + "-Xnodex2oat -Xmethod-trace -XX:LargeObjectSpace=map"))); + + auto&& map = parser_->ReleaseArgumentsMap(); + EXPECT_EQ(5u, map.Size()); + EXPECT_KEY_VALUE(map, M::Help, Unit{}); // NOLINT [whitespace/braces] [5] + EXPECT_KEY_VALUE(map, M::ForegroundHeapGrowthMultiplier, 0.5); + EXPECT_KEY_VALUE(map, M::Dex2Oat, false); + EXPECT_KEY_VALUE(map, M::MethodTrace, Unit{}); // NOLINT [whitespace/braces] [5] + EXPECT_KEY_VALUE(map, M::LargeObjectSpace, gc::space::LargeObjectSpaceType::kMap); +} // TEST_F +} // namespace art diff --git a/cmdline/cmdline_result.h b/cmdline/cmdline_result.h new file mode 100644 index 000000000..bf3a85da4 --- /dev/null +++ b/cmdline/cmdline_result.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_CMDLINE_RESULT_H_ +#define ART_CMDLINE_CMDLINE_RESULT_H_ + +#include +#include + +namespace art { + // Result of an attempt to process the command line arguments. If fails, specifies + // the specific error code and an error message. + // Use the value-carrying CmdlineParseResult to get an additional value out in a success case. + struct CmdlineResult { + enum Status { + kSuccess, + // Error codes: + kUsage, + kFailure, + kOutOfRange, + kUnknown, + }; + + // Short-hand for checking if the result was successful. + operator bool() const { + return IsSuccess(); + } + + // Check if the operation has succeeded. + bool IsSuccess() const { return status_ == kSuccess; } + // Check if the operation was not a success. + bool IsError() const { return status_ != kSuccess; } + // Get the specific status, regardless of whether it's failure or success. + Status GetStatus() const { return status_; } + + // Get the error message, *must* only be called for error status results. + const std::string& GetMessage() const { assert(IsError()); return message_; } + + // Constructor any status. No message. + explicit CmdlineResult(Status status) : status_(status) {} + + // Constructor with an error status, copying the message. + CmdlineResult(Status status, const std::string& message) + : status_(status), message_(message) { + assert(status != kSuccess); + } + + // Constructor with an error status, taking over the message. + CmdlineResult(Status status, std::string&& message) + : status_(status), message_(message) { + assert(status != kSuccess); + } + + // Make sure copying exists + CmdlineResult(const CmdlineResult& other) = default; + // Make sure moving is cheap + CmdlineResult(CmdlineResult&& other) = default; + + private: + const Status status_; + const std::string message_; + }; + + // TODO: code-generate this + static inline std::ostream& operator<<(std::ostream& stream, CmdlineResult::Status status) { + switch (status) { + case CmdlineResult::kSuccess: + stream << "kSuccess"; + break; + case CmdlineResult::kUsage: + stream << "kUsage"; + break; + case CmdlineResult::kFailure: + stream << "kFailure"; + break; + case CmdlineResult::kOutOfRange: + stream << "kOutOfRange"; + break; + case CmdlineResult::kUnknown: + stream << "kUnknown"; + break; + default: + UNREACHABLE(); + } + return stream; + } + +} // namespace art + +#endif // ART_CMDLINE_CMDLINE_RESULT_H_ diff --git a/cmdline/cmdline_type_parser.h b/cmdline/cmdline_type_parser.h new file mode 100644 index 000000000..fa5cdaf90 --- /dev/null +++ b/cmdline/cmdline_type_parser.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_CMDLINE_TYPE_PARSER_H_ +#define ART_CMDLINE_CMDLINE_TYPE_PARSER_H_ + +#include "cmdline_parse_result.h" + +namespace art { + +// Base class for user-defined CmdlineType specializations. +// +// Not strictly necessary, but if the specializations fail to Define all of these functions +// the compilation will fail. +template +struct CmdlineTypeParser { + // Return value of parsing attempts. Represents a Success(T value) or an Error(int code) + using Result = CmdlineParseResult; + + // Parse a single value for an argument definition out of the wildcard component. + // + // e.g. if the argument definition was "foo:_", and the user-provided input was "foo:bar", + // then args is "bar". + Result Parse(const std::string& args ATTRIBUTE_UNUSED) { + assert(false); + return Result::Failure("Missing type specialization and/or value map"); + } + + // Parse a value and append it into the existing value so far, for argument + // definitions which are marked with AppendValues(). + // + // The value is parsed out of the wildcard component as in Parse. + // + // If the initial value does not exist yet, a default value is created by + // value-initializing with 'T()'. + Result ParseAndAppend(const std::string& args ATTRIBUTE_UNUSED, + T& existing_value ATTRIBUTE_UNUSED) { + assert(false); + return Result::Failure("Missing type specialization and/or value map"); + } + + // Runtime type name of T, so that we can print more useful error messages. + static const char* Name() { assert(false); return "UnspecializedType"; } + + // Whether or not your type can parse argument definitions defined without a "_" + // e.g. -Xenable-profiler just mutates the existing profiler struct in-place + // so it doesn't need to do any parsing other than token recognition. + // + // If this is false, then either the argument definition has a _, from which the parsing + // happens, or the tokens get mapped to a value list/map from which a 1:1 matching occurs. + // + // This should almost *always* be false! + static constexpr bool kCanParseBlankless = false; + + protected: + // Don't accidentally initialize instances of this directly; they will assert at runtime. + CmdlineTypeParser() = default; +}; + + +} // namespace art + +#endif // ART_CMDLINE_CMDLINE_TYPE_PARSER_H_ diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h new file mode 100644 index 000000000..92210235e --- /dev/null +++ b/cmdline/cmdline_types.h @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ART_CMDLINE_CMDLINE_TYPES_H_ +#define ART_CMDLINE_CMDLINE_TYPES_H_ + +#define CMDLINE_NDEBUG 1 // Do not output any debugging information for parsing. + +#include "cmdline/memory_representation.h" +#include "cmdline/detail/cmdline_debug_detail.h" +#include "cmdline_type_parser.h" + +// Includes for the types that are being specialized +#include +#include "unit.h" +#include "jdwp/jdwp.h" +#include "runtime/base/logging.h" +#include "gc/collector_type.h" +#include "gc/space/large_object_space.h" +#include "profiler_options.h" + +namespace art { + +// The default specialization will always fail parsing the type from a string. +// Provide your own specialization that inherits from CmdlineTypeParser +// and implements either Parse or ParseAndAppend +// (only if the argument was defined with ::AppendValues()) but not both. +template +struct CmdlineType : CmdlineTypeParser { +}; + +// Specializations for CmdlineType follow: + +// Parse argument definitions for Unit-typed arguments. +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& args) { + if (args == "") { + return Result::Success(Unit{}); // NOLINT [whitespace/braces] [5] + } + return Result::Failure("Unexpected extra characters " + args); + } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& options) { + VLOG(jdwp) << "ParseJdwpOptions: " << options; + + if (options == "help") { + return Result::Usage( + "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n" + "Example: -Xrunjdwp:transport=dt_socket,address=localhost:6500,server=n\n"); + } + + const std::string s; + + std::vector pairs; + Split(options, ',', &pairs); + + JDWP::JdwpOptions jdwp_options = JDWP::JdwpOptions(); + std::stringstream error_stream; + + for (size_t i = 0; i < pairs.size(); ++i) { + std::string::size_type equals = pairs[i].find('='); + if (equals == std::string::npos) { + return Result::Failure(s + + "Can't parse JDWP option '" + pairs[i] + "' in '" + options + "'"); + } + + if (!ParseJdwpOption(pairs[i].substr(0, equals), + pairs[i].substr(equals + 1), + error_stream, + jdwp_options)) { + return Result::Failure(error_stream.str()); + } + } + + if (jdwp_options.transport == JDWP::kJdwpTransportUnknown) { + return Result::Failure(s + "Must specify JDWP transport: " + options); + } + if (!jdwp_options.server && (jdwp_options.host.empty() || jdwp_options.port == 0)) { + return Result::Failure(s + "Must specify JDWP host and port when server=n: " + options); + } + + return Result::Success(std::move(jdwp_options)); + } + + bool ParseJdwpOption(const std::string& name, const std::string& value, + std::ostream& error_stream, + JDWP::JdwpOptions& jdwp_options) { + if (name == "transport") { + if (value == "dt_socket") { + jdwp_options.transport = JDWP::kJdwpTransportSocket; + } else if (value == "dt_android_adb") { + jdwp_options.transport = JDWP::kJdwpTransportAndroidAdb; + } else { + error_stream << "JDWP transport not supported: " << value; + return false; + } + } else if (name == "server") { + if (value == "n") { + jdwp_options.server = false; + } else if (value == "y") { + jdwp_options.server = true; + } else { + error_stream << "JDWP option 'server' must be 'y' or 'n'"; + return false; + } + } else if (name == "suspend") { + if (value == "n") { + jdwp_options.suspend = false; + } else if (value == "y") { + jdwp_options.suspend = true; + } else { + error_stream << "JDWP option 'suspend' must be 'y' or 'n'"; + return false; + } + } else if (name == "address") { + /* this is either or : */ + std::string port_string; + jdwp_options.host.clear(); + std::string::size_type colon = value.find(':'); + if (colon != std::string::npos) { + jdwp_options.host = value.substr(0, colon); + port_string = value.substr(colon + 1); + } else { + port_string = value; + } + if (port_string.empty()) { + error_stream << "JDWP address missing port: " << value; + return false; + } + char* end; + uint64_t port = strtoul(port_string.c_str(), &end, 10); + if (*end != '\0' || port > 0xffff) { + error_stream << "JDWP address has junk in port field: " << value; + return false; + } + jdwp_options.port = port; + } else if (name == "launch" || name == "onthrow" || name == "oncaught" || name == "timeout") { + /* valid but unsupported */ + LOG(INFO) << "Ignoring JDWP option '" << name << "'='" << value << "'"; + } else { + LOG(INFO) << "Ignoring unrecognized JDWP option '" << name << "'='" << value << "'"; + } + + return true; + } + + static const char* Name() { return "JdwpOptions"; } +}; + +template +struct CmdlineType> : CmdlineTypeParser> { + using typename CmdlineTypeParser>::Result; + + Result Parse(const std::string arg) { + CMDLINE_DEBUG_LOG << "Parsing memory: " << arg << std::endl; + size_t val = ParseMemoryOption(arg.c_str(), Divisor); + CMDLINE_DEBUG_LOG << "Memory parsed to size_t value: " << val << std::endl; + + if (val == 0) { + return Result::Failure(std::string("not a valid memory value, or not divisible by ") + + std::to_string(Divisor)); + } + + return Result::Success(Memory(val)); + } + + // Parse a string of the form /[0-9]+[kKmMgG]?/, which is used to specify + // memory sizes. [kK] indicates kilobytes, [mM] megabytes, and + // [gG] gigabytes. + // + // "s" should point just past the "-Xm?" part of the string. + // "div" specifies a divisor, e.g. 1024 if the value must be a multiple + // of 1024. + // + // The spec says the -Xmx and -Xms options must be multiples of 1024. It + // doesn't say anything about -Xss. + // + // Returns 0 (a useless size) if "s" is malformed or specifies a low or + // non-evenly-divisible value. + // + static size_t ParseMemoryOption(const char* s, size_t div) { + // strtoul accepts a leading [+-], which we don't want, + // so make sure our string starts with a decimal digit. + if (isdigit(*s)) { + char* s2; + size_t val = strtoul(s, &s2, 10); + if (s2 != s) { + // s2 should be pointing just after the number. + // If this is the end of the string, the user + // has specified a number of bytes. Otherwise, + // there should be exactly one more character + // that specifies a multiplier. + if (*s2 != '\0') { + // The remainder of the string is either a single multiplier + // character, or nothing to indicate that the value is in + // bytes. + char c = *s2++; + if (*s2 == '\0') { + size_t mul; + if (c == '\0') { + mul = 1; + } else if (c == 'k' || c == 'K') { + mul = KB; + } else if (c == 'm' || c == 'M') { + mul = MB; + } else if (c == 'g' || c == 'G') { + mul = GB; + } else { + // Unknown multiplier character. + return 0; + } + + if (val <= std::numeric_limits::max() / mul) { + val *= mul; + } else { + // Clamp to a multiple of 1024. + val = std::numeric_limits::max() & ~(1024-1); + } + } else { + // There's more than one character after the numeric part. + return 0; + } + } + // The man page says that a -Xm value must be a multiple of 1024. + if (val % div == 0) { + return val; + } + } + } + return 0; + } + + static const char* Name() { return Memory::Name(); } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& str) { + char* end = nullptr; + errno = 0; + double value = strtod(str.c_str(), &end); + + if (*end != '\0') { + return Result::Failure("Failed to parse double from " + str); + } + if (errno == ERANGE) { + return Result::OutOfRange( + "Failed to parse double from " + str + "; overflow/underflow occurred"); + } + + return Result::Success(value); + } + + static const char* Name() { return "double"; } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& str) { + const char* begin = str.c_str(); + char* end; + + // Parse into a larger type (long long) because we can't use strtoul + // since it silently converts negative values into unsigned long and doesn't set errno. + errno = 0; + long long int result = strtoll(begin, &end, 10); // NOLINT [runtime/int] [4] + if (begin == end || *end != '\0' || errno == EINVAL) { + return Result::Failure("Failed to parse integer from " + str); + } else if ((errno == ERANGE) || // NOLINT [runtime/int] [4] + result < std::numeric_limits::min() + || result > std::numeric_limits::max() || result < 0) { + return Result::OutOfRange( + "Failed to parse integer from " + str + "; out of unsigned int range"); + } + + return Result::Success(static_cast(result)); + } + + static const char* Name() { return "unsigned integer"; } +}; + +// Lightweight nanosecond value type. Allows parser to convert user-input from milliseconds +// to nanoseconds automatically after parsing. +// +// All implicit conversion from uint64_t uses nanoseconds. +struct MillisecondsToNanoseconds { + // Create from nanoseconds. + MillisecondsToNanoseconds(uint64_t nanoseconds) : nanoseconds_(nanoseconds) { // NOLINT [runtime/explicit] [5] + } + + // Create from milliseconds. + static MillisecondsToNanoseconds FromMilliseconds(unsigned int milliseconds) { + return MillisecondsToNanoseconds(MsToNs(milliseconds)); + } + + // Get the underlying nanoseconds value. + uint64_t GetNanoseconds() const { + return nanoseconds_; + } + + // Get the milliseconds value [via a conversion]. Loss of precision will occur. + uint64_t GetMilliseconds() const { + return NsToMs(nanoseconds_); + } + + // Get the underlying nanoseconds value. + operator uint64_t() const { + return GetNanoseconds(); + } + + // Default constructors/copy-constructors. + MillisecondsToNanoseconds() : nanoseconds_(0ul) {} + MillisecondsToNanoseconds(const MillisecondsToNanoseconds& rhs) = default; + MillisecondsToNanoseconds(MillisecondsToNanoseconds&& rhs) = default; + + private: + uint64_t nanoseconds_; +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& str) { + CmdlineType uint_parser; + CmdlineParseResult res = uint_parser.Parse(str); + + if (res.IsSuccess()) { + return Result::Success(MillisecondsToNanoseconds::FromMilliseconds(res.GetValue())); + } else { + return Result::CastError(res); + } + } + + static const char* Name() { return "MillisecondsToNanoseconds"; } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& args) { + return Result::Success(args); + } + + Result ParseAndAppend(const std::string& args, + std::string& existing_value) { + if (existing_value.empty()) { + existing_value = args; + } else { + existing_value += ' '; + existing_value += args; + } + return Result::SuccessNoValue(); + } +}; + +template <> +struct CmdlineType> : CmdlineTypeParser> { + Result Parse(const std::string& args) { + assert(false && "Use AppendValues() for a string vector type"); + return Result::Failure("Unconditional failure: string vector must be appended: " + args); + } + + Result ParseAndAppend(const std::string& args, + std::vector& existing_value) { + existing_value.push_back(args); + return Result::SuccessNoValue(); + } + + static const char* Name() { return "std::vector"; } +}; + +template +struct ParseStringList { + explicit ParseStringList(std::vector&& list) : list_(list) {} + + operator std::vector() const { + return list_; + } + + operator std::vector&&() && { + return std::move(list_); + } + + size_t Size() const { + return list_.size(); + } + + std::string Join() const { + return art::Join(list_, Separator); + } + + static ParseStringList Split(const std::string& str) { + std::vector list; + art::Split(str, Separator, &list); + return ParseStringList(std::move(list)); + } + + ParseStringList() = default; + ParseStringList(const ParseStringList& rhs) = default; + ParseStringList(ParseStringList&& rhs) = default; + + private: + std::vector list_; +}; + +template +struct CmdlineType> : CmdlineTypeParser> { + using Result = CmdlineParseResult>; + + Result Parse(const std::string& args) { + return Result::Success(ParseStringList::Split(args)); + } + + static const char* Name() { return "ParseStringList"; } +}; + +static gc::CollectorType ParseCollectorType(const std::string& option) { + if (option == "MS" || option == "nonconcurrent") { + return gc::kCollectorTypeMS; + } else if (option == "CMS" || option == "concurrent") { + return gc::kCollectorTypeCMS; + } else if (option == "SS") { + return gc::kCollectorTypeSS; + } else if (option == "GSS") { + return gc::kCollectorTypeGSS; + } else if (option == "CC") { + return gc::kCollectorTypeCC; + } else if (option == "MC") { + return gc::kCollectorTypeMC; + } else { + return gc::kCollectorTypeNone; + } +} + +struct XGcOption { + // These defaults are used when the command line arguments for -Xgc: + // are either omitted completely or partially. + gc::CollectorType collector_type_ = kUseReadBarrier ? + // If RB is enabled (currently a build-time decision), + // use CC as the default GC. + gc::kCollectorTypeCC : + gc::kCollectorTypeDefault; + bool verify_pre_gc_heap_ = false; + bool verify_pre_sweeping_heap_ = kIsDebugBuild; + bool verify_post_gc_heap_ = false; + bool verify_pre_gc_rosalloc_ = kIsDebugBuild; + bool verify_pre_sweeping_rosalloc_ = false; + bool verify_post_gc_rosalloc_ = false; +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& option) { // -Xgc: already stripped + XGcOption xgc{}; // NOLINT [readability/braces] [4] + + std::vector gc_options; + Split(option, ',', &gc_options); + for (const std::string& gc_option : gc_options) { + gc::CollectorType collector_type = ParseCollectorType(gc_option); + if (collector_type != gc::kCollectorTypeNone) { + xgc.collector_type_ = collector_type; + } else if (gc_option == "preverify") { + xgc.verify_pre_gc_heap_ = true; + } else if (gc_option == "nopreverify") { + xgc.verify_pre_gc_heap_ = false; + } else if (gc_option == "presweepingverify") { + xgc.verify_pre_sweeping_heap_ = true; + } else if (gc_option == "nopresweepingverify") { + xgc.verify_pre_sweeping_heap_ = false; + } else if (gc_option == "postverify") { + xgc.verify_post_gc_heap_ = true; + } else if (gc_option == "nopostverify") { + xgc.verify_post_gc_heap_ = false; + } else if (gc_option == "preverify_rosalloc") { + xgc.verify_pre_gc_rosalloc_ = true; + } else if (gc_option == "nopreverify_rosalloc") { + xgc.verify_pre_gc_rosalloc_ = false; + } else if (gc_option == "presweepingverify_rosalloc") { + xgc.verify_pre_sweeping_rosalloc_ = true; + } else if (gc_option == "nopresweepingverify_rosalloc") { + xgc.verify_pre_sweeping_rosalloc_ = false; + } else if (gc_option == "postverify_rosalloc") { + xgc.verify_post_gc_rosalloc_ = true; + } else if (gc_option == "nopostverify_rosalloc") { + xgc.verify_post_gc_rosalloc_ = false; + } else if ((gc_option == "precise") || + (gc_option == "noprecise") || + (gc_option == "verifycardtable") || + (gc_option == "noverifycardtable")) { + // Ignored for backwards compatibility. + } else { + return Result::Usage(std::string("Unknown -Xgc option ") + gc_option); + } + } + + return Result::Success(std::move(xgc)); + } + + static const char* Name() { return "XgcOption"; } +}; + +struct BackgroundGcOption { + // If background_collector_type_ is kCollectorTypeNone, it defaults to the + // XGcOption::collector_type_ after parsing options. If you set this to + // kCollectorTypeHSpaceCompact then we will do an hspace compaction when + // we transition to background instead of a normal collector transition. + gc::CollectorType background_collector_type_; + + BackgroundGcOption(gc::CollectorType background_collector_type) // NOLINT [runtime/explicit] [5] + : background_collector_type_(background_collector_type) {} + BackgroundGcOption() + : background_collector_type_(gc::kCollectorTypeNone) { + + if (kUseReadBarrier) { + background_collector_type_ = gc::kCollectorTypeCC; // Disable background compaction for CC. + } + } + + operator gc::CollectorType() const { return background_collector_type_; } +}; + +template<> +struct CmdlineType + : CmdlineTypeParser, private BackgroundGcOption { + Result Parse(const std::string& substring) { + // Special handling for HSpaceCompact since this is only valid as a background GC type. + if (substring == "HSpaceCompact") { + background_collector_type_ = gc::kCollectorTypeHomogeneousSpaceCompact; + } else { + gc::CollectorType collector_type = ParseCollectorType(substring); + if (collector_type != gc::kCollectorTypeNone) { + background_collector_type_ = collector_type; + } else { + return Result::Failure(); + } + } + + BackgroundGcOption res = *this; + return Result::Success(res); + } + + static const char* Name() { return "BackgroundGcOption"; } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& options) { + LogVerbosity log_verbosity = LogVerbosity(); + + std::vector verbose_options; + Split(options, ',', &verbose_options); + for (size_t j = 0; j < verbose_options.size(); ++j) { + if (verbose_options[j] == "class") { + log_verbosity.class_linker = true; + } else if (verbose_options[j] == "compiler") { + log_verbosity.compiler = true; + } else if (verbose_options[j] == "gc") { + log_verbosity.gc = true; + } else if (verbose_options[j] == "heap") { + log_verbosity.heap = true; + } else if (verbose_options[j] == "jdwp") { + log_verbosity.jdwp = true; + } else if (verbose_options[j] == "jni") { + log_verbosity.jni = true; + } else if (verbose_options[j] == "monitor") { + log_verbosity.monitor = true; + } else if (verbose_options[j] == "profiler") { + log_verbosity.profiler = true; + } else if (verbose_options[j] == "signals") { + log_verbosity.signals = true; + } else if (verbose_options[j] == "startup") { + log_verbosity.startup = true; + } else if (verbose_options[j] == "third-party-jni") { + log_verbosity.third_party_jni = true; + } else if (verbose_options[j] == "threads") { + log_verbosity.threads = true; + } else if (verbose_options[j] == "verifier") { + log_verbosity.verifier = true; + } else { + return Result::Usage(std::string("Unknown -verbose option ") + verbose_options[j]); + } + } + + return Result::Success(log_verbosity); + } + + static const char* Name() { return "LogVerbosity"; } +}; + +// TODO: Replace with art::ProfilerOptions for the real thing. +struct TestProfilerOptions { + // Whether or not the applications should be profiled. + bool enabled_; + // Destination file name where the profiling data will be saved into. + std::string output_file_name_; + // Generate profile every n seconds. + uint32_t period_s_; + // Run profile for n seconds. + uint32_t duration_s_; + // Microseconds between samples. + uint32_t interval_us_; + // Coefficient to exponential backoff. + double backoff_coefficient_; + // Whether the profile should start upon app startup or be delayed by some random offset. + bool start_immediately_; + // Top K% of samples that are considered relevant when deciding if the app should be recompiled. + double top_k_threshold_; + // How much the top K% samples needs to change in order for the app to be recompiled. + double top_k_change_threshold_; + // The type of profile data dumped to the disk. + ProfileDataType profile_type_; + // The max depth of the stack collected by the profiler + uint32_t max_stack_depth_; + + TestProfilerOptions() : + enabled_(false), + output_file_name_(), + period_s_(0), + duration_s_(0), + interval_us_(0), + backoff_coefficient_(0), + start_immediately_(0), + top_k_threshold_(0), + top_k_change_threshold_(0), + profile_type_(ProfileDataType::kProfilerMethod), + max_stack_depth_(0) { + } + + TestProfilerOptions(const TestProfilerOptions& other) = default; + TestProfilerOptions(TestProfilerOptions&& other) = default; +}; + +static inline std::ostream& operator<<(std::ostream& stream, const TestProfilerOptions& options) { + stream << "TestProfilerOptions {" << std::endl; + +#define PRINT_TO_STREAM(field) \ + stream << #field << ": '" << options.field << "'" << std::endl; + + PRINT_TO_STREAM(enabled_); + PRINT_TO_STREAM(output_file_name_); + PRINT_TO_STREAM(period_s_); + PRINT_TO_STREAM(duration_s_); + PRINT_TO_STREAM(interval_us_); + PRINT_TO_STREAM(backoff_coefficient_); + PRINT_TO_STREAM(start_immediately_); + PRINT_TO_STREAM(top_k_threshold_); + PRINT_TO_STREAM(top_k_change_threshold_); + PRINT_TO_STREAM(profile_type_); + PRINT_TO_STREAM(max_stack_depth_); + + stream << "}"; + + return stream; +#undef PRINT_TO_STREAM +} + +template <> +struct CmdlineType : CmdlineTypeParser { + using Result = CmdlineParseResult; + + private: + using StringResult = CmdlineParseResult; + using DoubleResult = CmdlineParseResult; + + template + static Result ParseInto(TestProfilerOptions& options, + T TestProfilerOptions::*pField, + CmdlineParseResult&& result) { + assert(pField != nullptr); + + if (result.IsSuccess()) { + options.*pField = result.ReleaseValue(); + return Result::SuccessNoValue(); + } + + return Result::CastError(result); + } + + template + static Result ParseIntoRangeCheck(TestProfilerOptions& options, + T TestProfilerOptions::*pField, + CmdlineParseResult&& result, + T min, + T max) { + if (result.IsSuccess()) { + const T& value = result.GetValue(); + + if (value < min || value > max) { + CmdlineParseResult out_of_range = CmdlineParseResult::OutOfRange(value, min, max); + return Result::CastError(out_of_range); + } + } + + return ParseInto(options, pField, std::forward>(result)); + } + + static StringResult ParseStringAfterChar(const std::string& s, char c) { + std::string parsed_value; + + std::string::size_type colon = s.find(c); + if (colon == std::string::npos) { + return StringResult::Usage(std::string() + "Missing char " + c + " in option " + s); + } + // Add one to remove the char we were trimming until. + parsed_value = s.substr(colon + 1); + return StringResult::Success(parsed_value); + } + + static std::string RemovePrefix(const std::string& source) { + size_t prefix_idx = source.find(":"); + + if (prefix_idx == std::string::npos) { + return ""; + } + + return source.substr(prefix_idx + 1); + } + + public: + Result ParseAndAppend(const std::string& option, TestProfilerOptions& existing) { + // Special case which doesn't include a wildcard argument definition. + // We pass-it through as-is. + if (option == "-Xenable-profiler") { + existing.enabled_ = true; + return Result::SuccessNoValue(); + } + + // The rest of these options are always the wildcard from '-Xprofile-*' + std::string suffix = RemovePrefix(option); + + if (StartsWith(option, "filename:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::output_file_name_, + type_parser.Parse(suffix)); + } else if (StartsWith(option, "period:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::period_s_, + type_parser.Parse(suffix)); + } else if (StartsWith(option, "duration:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::duration_s_, + type_parser.Parse(suffix)); + } else if (StartsWith(option, "interval:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::interval_us_, + type_parser.Parse(suffix)); + } else if (StartsWith(option, "backoff:")) { + CmdlineType type_parser; + + return ParseIntoRangeCheck(existing, + &TestProfilerOptions::backoff_coefficient_, + type_parser.Parse(suffix), + 1.0, + 10.0); + + } else if (option == "start-immediately") { + existing.start_immediately_ = true; + return Result::SuccessNoValue(); + } else if (StartsWith(option, "top-k-threshold:")) { + CmdlineType type_parser; + + return ParseIntoRangeCheck(existing, + &TestProfilerOptions::top_k_threshold_, + type_parser.Parse(suffix), + 0.0, + 100.0); + } else if (StartsWith(option, "top-k-change-threshold:")) { + CmdlineType type_parser; + + return ParseIntoRangeCheck(existing, + &TestProfilerOptions::top_k_change_threshold_, + type_parser.Parse(suffix), + 0.0, + 100.0); + } else if (option == "type:method") { + existing.profile_type_ = kProfilerMethod; + return Result::SuccessNoValue(); + } else if (option == "type:stack") { + existing.profile_type_ = kProfilerBoundedStack; + return Result::SuccessNoValue(); + } else if (StartsWith(option, "max-stack-depth:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::max_stack_depth_, + type_parser.Parse(suffix)); + } else { + return Result::Failure(std::string("Invalid suboption '") + option + "'"); + } + } + + static const char* Name() { return "TestProfilerOptions"; } + static constexpr bool kCanParseBlankless = true; +}; + + +} // namespace art +#endif // ART_CMDLINE_CMDLINE_TYPES_H_ diff --git a/cmdline/detail/cmdline_debug_detail.h b/cmdline/detail/cmdline_debug_detail.h new file mode 100644 index 000000000..79028f490 --- /dev/null +++ b/cmdline/detail/cmdline_debug_detail.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_DETAIL_CMDLINE_DEBUG_DETAIL_H_ +#define ART_CMDLINE_DETAIL_CMDLINE_DEBUG_DETAIL_H_ + +#include +#ifndef CMDLINE_NDEBUG +#define CMDLINE_DEBUG_LOG std::cerr +#else +#define CMDLINE_DEBUG_LOG ::art::detail::debug_log_ignore() +#endif + +namespace art { + // Implementation details for some template querying. Don't look inside if you hate templates. + namespace detail { + struct debug_log_ignore { + // Ignore most of the normal operator<< usage. + template + debug_log_ignore& operator<<(const T&) { return *this; } + // Ignore std::endl and the like. + debug_log_ignore& operator<<(std::ostream& (*)(std::ostream&) ) { return *this; } + }; + } // namespace detail // NOLINT [readability/namespace] [5] +} // namespace art + +#endif // ART_CMDLINE_DETAIL_CMDLINE_DEBUG_DETAIL_H_ diff --git a/cmdline/detail/cmdline_parse_argument_detail.h b/cmdline/detail/cmdline_parse_argument_detail.h new file mode 100644 index 000000000..81ef36b08 --- /dev/null +++ b/cmdline/detail/cmdline_parse_argument_detail.h @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_DETAIL_CMDLINE_PARSE_ARGUMENT_DETAIL_H_ +#define ART_CMDLINE_DETAIL_CMDLINE_PARSE_ARGUMENT_DETAIL_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "cmdline/cmdline_parse_result.h" +#include "cmdline/token_range.h" +#include "cmdline/unit.h" +#include "cmdline/cmdline_types.h" + +namespace art { + // Implementation details for the parser. Do not look inside if you hate templates. + namespace detail { + // A non-templated base class for argument parsers. Used by the general parser + // to parse arguments, without needing to know the argument type at compile time. + // + // This is an application of the type erasure idiom. + struct CmdlineParseArgumentAny { + virtual ~CmdlineParseArgumentAny() {} + + // Attempt to parse this argument starting at arguments[position]. + // If the parsing succeeds, the parsed value will be saved as a side-effect. + // + // In most situations, the parsing will not match by returning kUnknown. In this case, + // no tokens were consumed and the position variable will not be updated. + // + // At other times, parsing may fail due to validation but the initial token was still matched + // (for example an out of range value, or passing in a string where an int was expected). + // In this case the tokens are still consumed, and the position variable will get incremented + // by all the consumed tokens. + // + // The # of tokens consumed by the parse attempt will be set as an out-parameter into + // consumed_tokens. The parser should skip this many tokens before parsing the next + // argument. + virtual CmdlineResult ParseArgument(const TokenRange& arguments, size_t* consumed_tokens) = 0; + // How many tokens should be taken off argv for parsing this argument. + // For example "--help" is just 1, "-compiler-option _" would be 2 (since there's a space). + // + // A [min,max] range is returned to represent argument definitions with multiple + // value tokens. (e.g. {"-h", "-h " } would return [1,2]). + virtual std::pair GetNumTokens() const = 0; + // Get the run-time typename of the argument type. + virtual const char* GetTypeName() const = 0; + // Try to do a close match, returning how many tokens were matched against this argument + // definition. More tokens is better. + // + // Do a quick match token-by-token, and see if they match. + // Any tokens with a wildcard in them are only matched up until the wildcard. + // If this is true, then the wildcard matching later on can still fail, so this is not + // a guarantee that the argument is correct, it's more of a strong hint that the + // user-provided input *probably* was trying to match this argument. + // + // Returns how many tokens were either matched (or ignored because there was a + // wildcard present). 0 means no match. If the Size() tokens are returned. + virtual size_t MaybeMatches(const TokenRange& tokens) = 0; + }; + + template + using EnableIfNumeric = std::enable_if::value>; + + template + using DisableIfNumeric = std::enable_if::value>; + + // Argument definition information, created by an ArgumentBuilder and an UntypedArgumentBuilder. + template + struct CmdlineParserArgumentInfo { + // This version will only be used if TArg is arithmetic and thus has the <= operators. + template // Necessary to get SFINAE to kick in. + bool CheckRange(const TArg& value, typename EnableIfNumeric::type* = 0) { + if (has_range_) { + return min_ <= value && value <= max_; + } + return true; + } + + // This version will be used at other times when TArg is not arithmetic. + template + bool CheckRange(const TArg&, typename DisableIfNumeric::type* = 0) { + assert(!has_range_); + return true; + } + + // Do a quick match token-by-token, and see if they match. + // Any tokens with a wildcard in them only match the prefix up until the wildcard. + // + // If this is true, then the wildcard matching later on can still fail, so this is not + // a guarantee that the argument is correct, it's more of a strong hint that the + // user-provided input *probably* was trying to match this argument. + size_t MaybeMatches(TokenRange token_list) const { + auto best_match = FindClosestMatch(token_list); + + return best_match.second; + } + + // Attempt to find the closest match (see MaybeMatches). + // + // Returns the token range that was the closest match and the # of tokens that + // this range was matched up until. + std::pair FindClosestMatch(TokenRange token_list) const { + const TokenRange* best_match_ptr = nullptr; + + size_t best_match = 0; + for (auto&& token_range : tokenized_names_) { + size_t this_match = token_range.MaybeMatches(token_list, std::string("_")); + + if (this_match > best_match) { + best_match_ptr = &token_range; + best_match = this_match; + } + } + + return std::make_pair(best_match_ptr, best_match); + } + + // Mark the argument definition as completed, do not mutate the object anymore after this + // call is done. + // + // Performs several sanity checks and token calculations. + void CompleteArgument() { + assert(names_.size() >= 1); + assert(!is_completed_); + + is_completed_ = true; + + size_t blank_count = 0; + size_t token_count = 0; + + size_t global_blank_count = 0; + size_t global_token_count = 0; + for (auto&& name : names_) { + std::string s(name); + + size_t local_blank_count = std::count(s.begin(), s.end(), '_'); + size_t local_token_count = std::count(s.begin(), s.end(), ' '); + + if (global_blank_count != 0) { + assert(local_blank_count == global_blank_count + && "Every argument descriptor string must have same amount of blanks (_)"); + } + + if (local_blank_count != 0) { + global_blank_count = local_blank_count; + blank_count++; + + assert(local_blank_count == 1 && "More than one blank is not supported"); + assert(s.back() == '_' && "The blank character must only be at the end of the string"); + } + + if (global_token_count != 0) { + assert(local_token_count == global_token_count + && "Every argument descriptor string must have same amount of tokens (spaces)"); + } + + if (local_token_count != 0) { + global_token_count = local_token_count; + token_count++; + } + + // Tokenize every name, turning it from a string to a token list. + tokenized_names_.clear(); + for (auto&& name1 : names_) { + // Split along ' ' only, removing any duplicated spaces. + tokenized_names_.push_back( + TokenRange::Split(name1, {' '}).RemoveToken(" ")); + } + + // remove the _ character from each of the token ranges + // we will often end up with an empty token (i.e. ["-XX", "_"] -> ["-XX", ""] + // and this is OK because we still need an empty token to simplify + // range comparisons + simple_names_.clear(); + + for (auto&& tokenized_name : tokenized_names_) { + simple_names_.push_back(tokenized_name.RemoveCharacter('_')); + } + } + + if (token_count != 0) { + assert(("Every argument descriptor string must have equal amount of tokens (spaces)" && + token_count == names_.size())); + } + + if (blank_count != 0) { + assert(("Every argument descriptor string must have an equal amount of blanks (_)" && + blank_count == names_.size())); + } + + using_blanks_ = blank_count > 0; + { + size_t smallest_name_token_range_size = + std::accumulate(tokenized_names_.begin(), tokenized_names_.end(), ~(0u), + [](size_t min, const TokenRange& cur) { + return std::min(min, cur.Size()); + }); + size_t largest_name_token_range_size = + std::accumulate(tokenized_names_.begin(), tokenized_names_.end(), 0u, + [](size_t max, const TokenRange& cur) { + return std::max(max, cur.Size()); + }); + + token_range_size_ = std::make_pair(smallest_name_token_range_size, + largest_name_token_range_size); + } + + if (has_value_list_) { + assert(names_.size() == value_list_.size() + && "Number of arg descriptors must match number of values"); + assert(!has_value_map_); + } + if (has_value_map_) { + if (!using_blanks_) { + assert(names_.size() == value_map_.size() && + "Since no blanks were specified, each arg is mapped directly into a mapped " + "value without parsing; sizes must match"); + } + + assert(!has_value_list_); + } + + if (!using_blanks_ && !CmdlineType::kCanParseBlankless) { + assert((has_value_map_ || has_value_list_) && + "Arguments without a blank (_) must provide either a value map or a value list"); + } + + TypedCheck(); + } + + // List of aliases for a single argument definition, e.g. {"-Xdex2oat", "-Xnodex2oat"}. + std::vector names_; + // Is there at least 1 wildcard '_' in the argument definition? + bool using_blanks_ = false; + // [min, max] token counts in each arg def + std::pair token_range_size_; + + // contains all the names in a tokenized form, i.e. as a space-delimited list + std::vector tokenized_names_; + + // contains the tokenized names, but with the _ character stripped + std::vector simple_names_; + + // For argument definitions created with '.AppendValues()' + // Meaning that parsing should mutate the existing value in-place if possible. + bool appending_values_ = false; + + // For argument definitions created with '.WithRange(min, max)' + bool has_range_ = false; + TArg min_; + TArg max_; + + // For argument definitions created with '.WithValueMap' + bool has_value_map_ = false; + std::vector> value_map_; + + // For argument definitions created with '.WithValues' + bool has_value_list_ = false; + std::vector value_list_; + + // Make sure there's a default constructor. + CmdlineParserArgumentInfo() = default; + + // Ensure there's a default move constructor. + CmdlineParserArgumentInfo(CmdlineParserArgumentInfo&&) = default; + + private: + // Perform type-specific checks at runtime. + template + void TypedCheck(typename std::enable_if::value>::type* = 0) { + assert(!using_blanks_ && + "Blanks are not supported in Unit arguments; since a Unit has no parse-able value"); + } + + void TypedCheck() {} + + bool is_completed_ = false; + }; + + // A virtual-implementation of the necessary argument information in order to + // be able to parse arguments. + template + struct CmdlineParseArgument : CmdlineParseArgumentAny { + explicit CmdlineParseArgument(CmdlineParserArgumentInfo&& argument_info, + std::function&& save_argument, + std::function&& load_argument) + : argument_info_(std::forward(argument_info)), + save_argument_(std::forward(save_argument)), + load_argument_(std::forward(load_argument)) { + } + + using UserTypeInfo = CmdlineType; + + virtual CmdlineResult ParseArgument(const TokenRange& arguments, size_t* consumed_tokens) { + assert(arguments.Size() > 0); + assert(consumed_tokens != nullptr); + + auto closest_match_res = argument_info_.FindClosestMatch(arguments); + size_t best_match_size = closest_match_res.second; + const TokenRange* best_match_arg_def = closest_match_res.first; + + if (best_match_size > arguments.Size()) { + // The best match has more tokens than were provided. + // Shouldn't happen in practice since the outer parser does this check. + return CmdlineResult(CmdlineResult::kUnknown, "Size mismatch"); + } + + assert(best_match_arg_def != nullptr); + *consumed_tokens = best_match_arg_def->Size(); + + if (!argument_info_.using_blanks_) { + return ParseArgumentSingle(arguments.Join(' ')); + } + + // Extract out the blank value from arguments + // e.g. for a def of "foo:_" and input "foo:bar", blank_value == "bar" + std::string blank_value = ""; + size_t idx = 0; + for (auto&& def_token : *best_match_arg_def) { + auto&& arg_token = arguments[idx]; + + // Does this definition-token have a wildcard in it? + if (def_token.find('_') == std::string::npos) { + // No, regular token. Match 1:1 against the argument token. + bool token_match = def_token == arg_token; + + if (!token_match) { + return CmdlineResult(CmdlineResult::kFailure, + std::string("Failed to parse ") + best_match_arg_def->GetToken(0) + + " at token " + std::to_string(idx)); + } + } else { + // This is a wild-carded token. + TokenRange def_split_wildcards = TokenRange::Split(def_token, {'_'}); + + // Extract the wildcard contents out of the user-provided arg_token. + std::unique_ptr arg_matches = + def_split_wildcards.MatchSubstrings(arg_token, "_"); + if (arg_matches == nullptr) { + return CmdlineResult(CmdlineResult::kFailure, + std::string("Failed to parse ") + best_match_arg_def->GetToken(0) + + ", with a wildcard pattern " + def_token + + " at token " + std::to_string(idx)); + } + + // Get the corresponding wildcard tokens from arg_matches, + // and concatenate it to blank_value. + for (size_t sub_idx = 0; + sub_idx < def_split_wildcards.Size() && sub_idx < arg_matches->Size(); ++sub_idx) { + if (def_split_wildcards[sub_idx] == "_") { + blank_value += arg_matches->GetToken(sub_idx); + } + } + } + + ++idx; + } + + return ParseArgumentSingle(blank_value); + } + + private: + virtual CmdlineResult ParseArgumentSingle(const std::string& argument) { + // TODO: refactor to use LookupValue for the value lists/maps + + // Handle the 'WithValueMap(...)' argument definition + if (argument_info_.has_value_map_) { + for (auto&& value_pair : argument_info_.value_map_) { + const char* name = value_pair.first; + + if (argument == name) { + return SaveArgument(value_pair.second); + } + } + + // Error case: Fail, telling the user what the allowed values were. + std::vector allowed_values; + for (auto&& value_pair : argument_info_.value_map_) { + const char* name = value_pair.first; + allowed_values.push_back(name); + } + + std::string allowed_values_flat = Join(allowed_values, ','); + return CmdlineResult(CmdlineResult::kFailure, + "Argument value '" + argument + "' does not match any of known valid" + "values: {" + allowed_values_flat + "}"); + } + + // Handle the 'WithValues(...)' argument definition + if (argument_info_.has_value_list_) { + size_t arg_def_idx = 0; + for (auto&& value : argument_info_.value_list_) { + auto&& arg_def_token = argument_info_.names_[arg_def_idx]; + + if (arg_def_token == argument) { + return SaveArgument(value); + } + ++arg_def_idx; + } + + assert(arg_def_idx + 1 == argument_info_.value_list_.size() && + "Number of named argument definitions must match number of values defined"); + + // Error case: Fail, telling the user what the allowed values were. + std::vector allowed_values; + for (auto&& arg_name : argument_info_.names_) { + allowed_values.push_back(arg_name); + } + + std::string allowed_values_flat = Join(allowed_values, ','); + return CmdlineResult(CmdlineResult::kFailure, + "Argument value '" + argument + "' does not match any of known valid" + "values: {" + allowed_values_flat + "}"); + } + + // Handle the regular case where we parsed an unknown value from a blank. + UserTypeInfo type_parser; + + if (argument_info_.appending_values_) { + TArg& existing = load_argument_(); + CmdlineParseResult result = type_parser.ParseAndAppend(argument, existing); + + assert(!argument_info_.has_range_); + + return result; + } + + CmdlineParseResult result = type_parser.Parse(argument); + + if (result.IsSuccess()) { + TArg& value = result.GetValue(); + + // Do a range check for 'WithRange(min,max)' argument definition. + if (!argument_info_.CheckRange(value)) { + return CmdlineParseResult::OutOfRange( + value, argument_info_.min_, argument_info_.max_); + } + + return SaveArgument(value); + } + + // Some kind of type-specific parse error. Pass the result as-is. + CmdlineResult raw_result = std::move(result); + return raw_result; + } + + public: + virtual const char* GetTypeName() const { + // TODO: Obviate the need for each type specialization to hardcode the type name + return UserTypeInfo::Name(); + } + + // How many tokens should be taken off argv for parsing this argument. + // For example "--help" is just 1, "-compiler-option _" would be 2 (since there's a space). + // + // A [min,max] range is returned to represent argument definitions with multiple + // value tokens. (e.g. {"-h", "-h " } would return [1,2]). + virtual std::pair GetNumTokens() const { + return argument_info_.token_range_size_; + } + + // See if this token range might begin the same as the argument definition. + virtual size_t MaybeMatches(const TokenRange& tokens) { + return argument_info_.MaybeMatches(tokens); + } + + private: + CmdlineResult SaveArgument(const TArg& value) { + assert(!argument_info_.appending_values_ + && "If the values are being appended, then the updated parse value is " + "updated by-ref as a side effect and shouldn't be stored directly"); + TArg val = value; + save_argument_(val); + return CmdlineResult(CmdlineResult::kSuccess); + } + + CmdlineParserArgumentInfo argument_info_; + std::function save_argument_; + std::function load_argument_; + }; + } // namespace detail // NOLINT [readability/namespace] [5] [whitespace/comments] [2] +} // namespace art + +#endif // ART_CMDLINE_DETAIL_CMDLINE_PARSE_ARGUMENT_DETAIL_H_ diff --git a/cmdline/detail/cmdline_parser_detail.h b/cmdline/detail/cmdline_parser_detail.h new file mode 100644 index 000000000..9b43bb0f5 --- /dev/null +++ b/cmdline/detail/cmdline_parser_detail.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_DETAIL_CMDLINE_PARSER_DETAIL_H_ +#define ART_CMDLINE_DETAIL_CMDLINE_PARSER_DETAIL_H_ + +#include +#include +#include + +namespace art { + // Implementation details for some template querying. Don't look inside if you hate templates. + namespace detail { + template + typename std::remove_reference::type& FakeReference(); + + // SupportsInsertionOperator::value will evaluate to a boolean, + // whose value is true if the TStream class supports the << operator against T, + // and false otherwise. + template + struct SupportsInsertionOperator { + private: + template + static std::true_type InsertionOperatorTest(TStream& os, const T& value, + std::remove_reference* = 0); // NOLINT [whitespace/operators] [3] + + template + static std::false_type InsertionOperatorTest(TStream& os, const T& ... args); + + public: + static constexpr bool value = + decltype(InsertionOperatorTest(FakeReference(), std::declval()))::value; + }; + + template + struct SupportsEqualityOperatorImpl; + + template + struct SupportsEqualityOperatorImpl { + private: + template + static std::true_type EqualityOperatorTest(const TL& left, const TR& right, + std::remove_reference* = 0); // NOLINT [whitespace/operators] [3] + + template + static std::false_type EqualityOperatorTest(const TL& left, const T& ... args); + + public: + static constexpr bool value = + decltype(EqualityOperatorTest(std::declval(), std::declval()))::value; + }; + + // Partial specialization when TLeft/TRight are both floating points. + // This is a work-around because decltype(floatvar1 == floatvar2) + // will not compile with clang: + // error: comparing floating point with == or != is unsafe [-Werror,-Wfloat-equal] + template + struct SupportsEqualityOperatorImpl { + static constexpr bool value = true; + }; + + // SupportsEqualityOperatorImpl::value will evaluate to a boolean, + // whose value is true if T1 can be compared against T2 with ==, + // and false otherwise. + template + struct SupportsEqualityOperator : + SupportsEqualityOperatorImpl::value + && std::is_floating_point::value> { + }; + + // Convert any kind of type to an std::string, even if there's no + // serialization support for it. Unknown types get converted to an + // an arbitrary value. + // + // Meant for printing user-visible errors or unit test failures only. + template + std::string ToStringAny(const T& value, + typename std::enable_if< + SupportsInsertionOperator::value>::type* = 0) { + std::stringstream stream; + stream << value; + return stream.str(); + } + + template + std::string ToStringAny(const std::vector value, + typename std::enable_if< + SupportsInsertionOperator::value>::type* = 0) { + std::stringstream stream; + stream << "vector{"; + + for (size_t i = 0; i < value.size(); ++i) { + stream << ToStringAny(value[i]); + + if (i != value.size() - 1) { + stream << ','; + } + } + + stream << "}"; + return stream.str(); + } + + template + std::string ToStringAny(const T&, + typename std::enable_if< + !SupportsInsertionOperator::value>::type* = 0 + ) { + return std::string("(unknown type [no operator<< implemented] for )"); + } + } // namespace detail // NOLINT [readability/namespace] [5] +} // namespace art + +#endif // ART_CMDLINE_DETAIL_CMDLINE_PARSER_DETAIL_H_ diff --git a/cmdline/memory_representation.h b/cmdline/memory_representation.h new file mode 100644 index 000000000..93387de83 --- /dev/null +++ b/cmdline/memory_representation.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_MEMORY_REPRESENTATION_H_ +#define ART_CMDLINE_MEMORY_REPRESENTATION_H_ + +#include +#include +#include +#include "utils.h" + +namespace art { + +// An integral representation of bytes of memory. +// The underlying runtime size_t value is guaranteed to be a multiple of Divisor. +template +struct Memory { + static_assert(IsPowerOfTwo(Divisor), "Divisor must be a power of 2"); + + static Memory FromBytes(size_t bytes) { + assert(bytes % Divisor == 0); + return Memory(bytes); + } + + Memory() : Value(0u) {} + Memory(size_t value) : Value(value) { // NOLINT [runtime/explicit] [5] + assert(value % Divisor == 0); + } + operator size_t() const { return Value; } + + size_t ToBytes() const { + return Value; + } + + static constexpr size_t kDivisor = Divisor; + + static const char* Name() { + static std::string str; + if (str.empty()) { + str = "Memory<" + std::to_string(Divisor) + '>'; + } + + return str.c_str(); + } + + size_t Value; +}; + +template +std::ostream& operator<<(std::ostream& stream, Memory memory) { + return stream << memory.Value << '*' << Divisor; +} + +using MemoryKiB = Memory<1024>; + +} // namespace art + +#endif // ART_CMDLINE_MEMORY_REPRESENTATION_H_ diff --git a/cmdline/token_range.h b/cmdline/token_range.h new file mode 100644 index 000000000..50c54fea4 --- /dev/null +++ b/cmdline/token_range.h @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_TOKEN_RANGE_H_ +#define ART_CMDLINE_TOKEN_RANGE_H_ + +#include +#include +#include +#include +#include + +namespace art { +// A range of tokens to make token matching algorithms easier. +// +// We try really hard to avoid copying and store only a pointer and iterators to the +// interiors of the vector, so a typical copy constructor never ends up doing a deep copy. +// It is up to the user to play nice and not to mutate the strings in-place. +// +// Tokens are only copied if a mutating operation is performed (and even then only +// if it *actually* mutates the token). +struct TokenRange { + // Short-hand for a vector of strings. A single string and a token is synonymous. + using TokenList = std::vector; + + // Copying-from-vector constructor. + explicit TokenRange(const TokenList& token_list) + : token_list_(new TokenList(token_list)), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + + // Copying-from-iterator constructor + template + explicit TokenRange(ForwardIterator it_begin, ForwardIterator it_end) + : token_list_(new TokenList(it_begin, it_end)), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + +#if 0 + // Copying-from-vector constructor. + TokenRange(const TokenList& token_list ATTRIBUTE_UNUSED, + TokenList::const_iterator it_begin, + TokenList::const_iterator it_end) + : token_list_(new TokenList(it_begin, it_end)), + begin_(token_list_->begin()), + end_(token_list_->end()) { + assert(it_begin >= token_list.begin()); + assert(it_end <= token_list.end()); + } +#endif + + // Copying from char array constructor, convertings into tokens (strings) along the way. + TokenRange(const char* token_list[], size_t length) + : token_list_(new TokenList(&token_list[0], &token_list[length])), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + + // Non-copying move-from-vector constructor. Takes over the token vector. + explicit TokenRange(TokenList&& token_list) + : token_list_(new TokenList(std::forward(token_list))), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + + // Non-copying constructor. Retain reference to existing list of tokens. + TokenRange(std::shared_ptr token_list, + TokenList::const_iterator it_begin, + TokenList::const_iterator it_end) + : token_list_(token_list), + begin_(it_begin), + end_(it_end) { + assert(it_begin >= token_list->begin()); + assert(it_end <= token_list->end()); + } + + // Non-copying copy constructor. + TokenRange(const TokenRange& other) = default; + + // Non-copying move constructor. + TokenRange(TokenRange&& other) = default; + + // Non-copying constructor. Retains reference to an existing list of tokens, with offset. + explicit TokenRange(std::shared_ptr token_list) + : token_list_(token_list), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + + // Iterator type for begin() and end(). Guaranteed to be a RandomAccessIterator. + using iterator = TokenList::const_iterator; + + // Iterator type for const begin() and const end(). Guaranteed to be a RandomAccessIterator. + using const_iterator = iterator; + + // Create a token range by splitting a string. Each separator gets their own token. + // Since the separator are retained as tokens, it might be useful to call + // RemoveToken afterwards. + static TokenRange Split(const std::string& string, std::initializer_list separators) { + TokenList new_token_list; + + std::string tok; + for (auto&& c : string) { + for (char sep : separators) { + if (c == sep) { + // We spotted a separator character. + // Push back everything before the last separator as a new token. + // Push back the separator as a token. + if (!tok.empty()) { + new_token_list.push_back(tok); + tok = ""; + } + new_token_list.push_back(std::string() + sep); + } else { + // Build up the token with another character. + tok += c; + } + } + } + + if (!tok.empty()) { + new_token_list.push_back(tok); + } + + return TokenRange(std::move(new_token_list)); + } + + // A RandomAccessIterator to the first element in this range. + iterator begin() const { + return begin_; + } + + // A RandomAccessIterator to one past the last element in this range. + iterator end() const { + return end_; + } + + // The size of the range, i.e. how many tokens are in it. + size_t Size() const { + return std::distance(begin_, end_); + } + + // Are there 0 tokens in this range? + bool IsEmpty() const { + return Size() > 0; + } + + // Look up a token by it's offset. + const std::string& GetToken(size_t offset) const { + assert(offset < Size()); + return *(begin_ + offset); + } + + // Does this token range equal the other range? + // Equality is defined as having both the same size, and + // each corresponding token being equal. + bool operator==(const TokenRange& other) const { + if (this == &other) { + return true; + } + + if (Size() != other.Size()) { + return false; + } + + return std::equal(begin(), end(), other.begin()); + } + + // Look up the token at the requested index. + const std::string& operator[](int index) const { + assert(index >= 0 && static_cast(index) < Size()); + return *(begin() + index); + } + + // Does this current range start with the other range? + bool StartsWith(const TokenRange& other) const { + if (this == &other) { + return true; + } + + if (Size() < other.Size()) { + return false; + } + + auto& smaller = Size() < other.Size() ? *this : other; + auto& greater = Size() < other.Size() ? other : *this; + + return std::equal(smaller.begin(), smaller.end(), greater.begin()); + } + + // Remove all characters 'c' from each token, potentially copying the underlying tokens. + TokenRange RemoveCharacter(char c) const { + TokenList new_token_list(begin(), end()); + + bool changed = false; + for (auto&& token : new_token_list) { + auto it = std::remove_if(token.begin(), token.end(), [&](char ch) { + if (ch == c) { + changed = true; + return true; + } + return false; + }); + token.erase(it, token.end()); + } + + if (!changed) { + return *this; + } + + return TokenRange(std::move(new_token_list)); + } + + // Remove all tokens matching this one, potentially copying the underlying tokens. + TokenRange RemoveToken(const std::string& token) { + return RemoveIf([&](const std::string& tok) { return tok == token; }); + } + + // Discard all empty tokens, potentially copying the underlying tokens. + TokenRange DiscardEmpty() const { + return RemoveIf([](const std::string& token) { return token.empty(); }); + } + + // Create a non-copying subset of this range. + // Length is trimmed so that the Slice does not go out of range. + TokenRange Slice(size_t offset, size_t length = std::string::npos) const { + assert(offset < Size()); + + if (length != std::string::npos && offset + length > Size()) { + length = Size() - offset; + } + + iterator it_end; + if (length == std::string::npos) { + it_end = end(); + } else { + it_end = begin() + offset + length; + } + + return TokenRange(token_list_, begin() + offset, it_end); + } + + // Try to match the string with tokens from this range. + // Each token is used to match exactly once (after which the next token is used, and so on). + // The matching happens from left-to-right in a non-greedy fashion. + // If the currently-matched token is the wildcard, then the new outputted token will + // contain as much as possible until the next token is matched. + // + // For example, if this == ["a:", "_", "b:] and "_" is the match string, then + // MatchSubstrings on "a:foob:" will yield: ["a:", "foo", "b:"] + // + // Since the string matching can fail (e.g. ["foo"] against "bar"), then this + // function can fail, in which cause it will return null. + std::unique_ptr MatchSubstrings(const std::string& string, + const std::string& wildcard) const { + TokenList new_token_list; + + size_t wildcard_idx = std::string::npos; + size_t string_idx = 0; + + // Function to push all the characters matched as a wildcard so far + // as a brand new token. It resets the wildcard matching. + // Empty wildcards are possible and ok, but only if wildcard matching was on. + auto maybe_push_wildcard_token = [&]() { + if (wildcard_idx != std::string::npos) { + size_t wildcard_length = string_idx - wildcard_idx; + std::string wildcard_substr = string.substr(wildcard_idx, wildcard_length); + new_token_list.push_back(std::move(wildcard_substr)); + + wildcard_idx = std::string::npos; + } + }; + + for (iterator it = begin(); it != end(); ++it) { + const std::string& tok = *it; + + if (tok == wildcard) { + maybe_push_wildcard_token(); + wildcard_idx = string_idx; + continue; + } + + size_t next_token_idx = string.find(tok); + if (next_token_idx == std::string::npos) { + // Could not find token at all + return nullptr; + } else if (next_token_idx != string_idx && wildcard_idx == std::string::npos) { + // Found the token at a non-starting location, and we weren't + // trying to parse the wildcard. + return nullptr; + } + + new_token_list.push_back(string.substr(next_token_idx, tok.size())); + maybe_push_wildcard_token(); + string_idx += tok.size(); + } + + size_t remaining = string.size() - string_idx; + if (remaining > 0) { + if (wildcard_idx == std::string::npos) { + // Some characters were still remaining in the string, + // but it wasn't trying to match a wildcard. + return nullptr; + } + } + + // If some characters are remaining, the rest must be a wildcard. + string_idx += remaining; + maybe_push_wildcard_token(); + + return std::unique_ptr(new TokenRange(std::move(new_token_list))); + } + + // Do a quick match token-by-token, and see if they match. + // Any tokens with a wildcard in them are only matched up until the wildcard. + // If this is true, then the wildcard matching later on can still fail, so this is not + // a guarantee that the argument is correct, it's more of a strong hint that the + // user-provided input *probably* was trying to match this argument. + // + // Returns how many tokens were either matched (or ignored because there was a + // wildcard present). 0 means no match. If the size() tokens are returned. + size_t MaybeMatches(const TokenRange& token_list, const std::string& wildcard) const { + auto token_it = token_list.begin(); + auto token_end = token_list.end(); + auto name_it = begin(); + auto name_end = end(); + + size_t matched_tokens = 0; + + while (token_it != token_end && name_it != name_end) { + // Skip token matching when the corresponding name has a wildcard in it. + const std::string& name = *name_it; + + size_t wildcard_idx = name.find(wildcard); + if (wildcard_idx == std::string::npos) { // No wildcard present + // Did the definition token match the user token? + if (name != *token_it) { + return matched_tokens; + } + } else { + std::string name_prefix = name.substr(0, wildcard_idx); + + // Did the user token start with the up-to-the-wildcard prefix? + if (!StartsWith(*token_it, name_prefix)) { + return matched_tokens; + } + } + + ++token_it; + ++name_it; + ++matched_tokens; + } + + // If we got this far, it's either a full match or the token list was too short. + return matched_tokens; + } + + // Flatten the token range by joining every adjacent token with the separator character. + // e.g. ["hello", "world"].join('$') == "hello$world" + std::string Join(char separator) const { + TokenList tmp(begin(), end()); + return art::Join(tmp, separator); + // TODO: Join should probably take an offset or iterators + } + + private: + static bool StartsWith(const std::string& larger, const std::string& smaller) { + if (larger.size() >= smaller.size()) { + return std::equal(smaller.begin(), smaller.end(), larger.begin()); + } + + return false; + } + + template + TokenRange RemoveIf(const TPredicate& predicate) const { + // If any of the tokens in the token lists are empty, then + // we need to remove them and compress the token list into a smaller one. + bool remove = false; + for (auto it = begin_; it != end_; ++it) { + auto&& token = *it; + + if (predicate(token)) { + remove = true; + break; + } + } + + // Actually copy the token list and remove the tokens that don't match our predicate. + if (remove) { + auto token_list = std::make_shared(begin(), end()); + TokenList::iterator new_end = + std::remove_if(token_list->begin(), token_list->end(), predicate); + token_list->erase(new_end, token_list->end()); + + assert(token_list_->size() > token_list->size() && "Nothing was actually removed!"); + + return TokenRange(token_list); + } + + return *this; + } + + const std::shared_ptr> token_list_; + const iterator begin_; + const iterator end_; +}; +} // namespace art + +#endif // ART_CMDLINE_TOKEN_RANGE_H_ diff --git a/cmdline/unit.h b/cmdline/unit.h new file mode 100644 index 000000000..6b53b1832 --- /dev/null +++ b/cmdline/unit.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_UNIT_H_ +#define ART_CMDLINE_UNIT_H_ + +namespace art { + +// Used for arguments that simply indicate presence (e.g. "-help") without any values. +struct Unit { + // Avoid 'Conditional jump or move depends on uninitialised value(s)' errors + // when running valgrind by specifying a user-defined constructor. + Unit() {} + ~Unit() {} + bool operator==(Unit) const { + return true; + } +}; + +} // namespace art + +#endif // ART_CMDLINE_UNIT_H_ diff --git a/runtime/Android.mk b/runtime/Android.mk index 30cf58005..907d8840a 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -139,6 +139,7 @@ LIBART_COMMON_SRC_FILES := \ reference_table.cc \ reflection.cc \ runtime.cc \ + runtime_options.cc \ signal_catcher.cc \ stack.cc \ thread.cc \ @@ -457,7 +458,9 @@ $$(ENUM_OPERATOR_OUT_GEN): $$(GENERATED_SRC_DIR)/%_operator_out.cc : $(LOCAL_PAT endif LOCAL_C_INCLUDES += $$(ART_C_INCLUDES) + LOCAL_C_INCLUDES += art/cmdline LOCAL_C_INCLUDES += art/sigchainlib + LOCAL_C_INCLUDES += art LOCAL_SHARED_LIBRARIES := libnativehelper libnativebridge libsigchain LOCAL_SHARED_LIBRARIES += libbacktrace diff --git a/runtime/base/variant_map.h b/runtime/base/variant_map.h new file mode 100644 index 000000000..cf7977edc --- /dev/null +++ b/runtime/base/variant_map.h @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_BASE_VARIANT_MAP_H_ +#define ART_RUNTIME_BASE_VARIANT_MAP_H_ + +#include +#include +#include + +namespace art { + +// +// A variant map is a heterogenous, type safe key->value map. It allows +// for multiple different value types to be stored dynamically in the same map. +// +// It provides the following interface in a nutshell: +// +// struct VariantMap { +// template +// TValue* Get(Key key); // nullptr if the value was never set, otherwise the value. +// +// template +// void Set(Key key, TValue value); +// }; +// +// Since the key is strongly typed at compile-time, it is impossible to accidentally +// read/write a value with a different type than the key at either compile-time or run-time. +// +// Do not use VariantMap/VariantMapKey directly. Instead subclass each of them and use +// the subclass, for example: +// +// template +// struct FruitMapKey : VariantMapKey { +// FruitMapKey() {} +// }; +// +// struct FruitMap : VariantMap { +// // This 'using' line is necessary to inherit the variadic constructor. +// using VariantMap::VariantMap; +// +// // Make the next '4' usages of Key slightly shorter to type. +// template +// using Key = FruitMapKey; +// +// static const Key Apple; +// static const Key Orange; +// static const Key Banana; +// }; +// +// const FruitMap::Key FruitMap::Apple; +// const FruitMap::Key FruitMap::Orange; +// const FruitMap::Key Banana; +// +// See variant_map_test.cc for more examples. +// + +// Implementation details for VariantMap. +namespace detail { + // Allocate a unique counter value each time it's called. + struct VariantMapKeyCounterAllocator { + static size_t AllocateCounter() { + static size_t counter = 0; + counter++; + + return counter; + } + }; + + // Type-erased version of VariantMapKey + struct VariantMapKeyRaw { + // TODO: this may need to call a virtual function to support string comparisons + bool operator<(const VariantMapKeyRaw& other) const { + return key_counter_ < other.key_counter_; + } + + // The following functions need to be virtual since we don't know the compile-time type anymore: + + // Clone the key, creating a copy of the contents. + virtual VariantMapKeyRaw* Clone() const = 0; + + // Delete a value whose runtime type is that of the non-erased key's TValue. + virtual void ValueDelete(void* value) const = 0; + + // Clone a value whose runtime type is that of the non-erased key's TValue. + virtual void* ValueClone(void* value) const = 0; + + // Compare one key to another (same as operator<). + virtual bool Compare(const VariantMapKeyRaw* other) const { + if (other == nullptr) { + return false; + } + return key_counter_ < other->key_counter_; + } + + virtual ~VariantMapKeyRaw() {} + + protected: + VariantMapKeyRaw() + : key_counter_(VariantMapKeyCounterAllocator::AllocateCounter()) {} + // explicit VariantMapKeyRaw(size_t counter) + // : key_counter_(counter) {} + + size_t GetCounter() const { + return key_counter_; + } + + protected: + // Avoid the object slicing problem; use Clone() instead. + VariantMapKeyRaw(const VariantMapKeyRaw& other) = default; + VariantMapKeyRaw(VariantMapKeyRaw&& other) = default; + + private: + size_t key_counter_; // Runtime type ID. Unique each time a new type is reified. + }; +} // namespace detail + +// The base type for keys used by the VariantMap. Users must subclass this type. +template +struct VariantMapKey : detail::VariantMapKeyRaw { + // Instantiate a default value for this key. If an explicit default value was provided + // then that is used. Otherwise, the default value for the type TValue{} is returned. + TValue CreateDefaultValue() const { + if (default_value_ == nullptr) { + return TValue{}; // NOLINT [readability/braces] [4] + } else { + return TValue(*default_value_); + } + } + + protected: + // explicit VariantMapKey(size_t counter) : detail::VariantMapKeyRaw(counter) {} + explicit VariantMapKey(const TValue& default_value) + : default_value_(std::make_shared(default_value)) {} + explicit VariantMapKey(TValue&& default_value) + : default_value_(std::make_shared(default_value)) {} + VariantMapKey() {} + virtual ~VariantMapKey() {} + + private: + virtual VariantMapKeyRaw* Clone() const { + return new VariantMapKey(*this); + } + + virtual void* ValueClone(void* value) const { + if (value == nullptr) { + return nullptr; + } + + TValue* strong_value = reinterpret_cast(value); + return new TValue(*strong_value); + } + + virtual void ValueDelete(void* value) const { + if (value == nullptr) { + return; + } + + // Smartly invoke the proper delete/delete[]/etc + const std::default_delete deleter = std::default_delete(); + deleter(reinterpret_cast(value)); + } + + VariantMapKey(const VariantMapKey& other) = default; + VariantMapKey(VariantMapKey&& other) = default; + + template class TKey> friend struct VariantMap; + + // Store a prototype of the key's default value, for usage with VariantMap::GetOrDefault + std::shared_ptr default_value_; +}; + +// Implementation details for a stringified VariantMapStringKey. +namespace detail { + struct VariantMapStringKeyRegistry { + // TODO + }; +} // namespace detail + +// Alternative base type for all keys used by VariantMap, supports runtime strings as the name. +template +struct VariantMapStringKey : VariantMapKey { + explicit VariantMapStringKey(const char* name) + : // VariantMapKey(/*std::hash()(name)*/), + name_(name) { + } + + private: + const char* name_; +}; + +// A variant map allows type-safe heteregeneous key->value mappings. +// All possible key types must be specified at compile-time. Values may be added/removed +// at runtime. +template class TKey> +struct VariantMap { + // Allow users of this static interface to use the key type. + template + using Key = TKey; + + // Look up the value from the key. The pointer becomes invalid if this key is overwritten/removed. + // A null value is returned only when the key does not exist in this map. + template + const TValue* Get(const TKey& key) const { + return GetValuePtr(key); + } + + // Look up the value from the key. The pointer becomes invalid if this key is overwritten/removed. + // A null value is returned only when the key does not exist in this map. + template + TValue* Get(const TKey& key) { + return GetValuePtr(key); + } + + // Lookup the value from the key. If it was not set in the map, return the default value. + // The default value is either the key's default, or TValue{} if the key doesn't have a default. + template + TValue GetOrDefault(const TKey& key) const { + auto* ptr = Get(key); + return (ptr == nullptr) ? key.CreateDefaultValue() : *ptr; + } + + private: + // TODO: move to detail, or make it more generic like a ScopeGuard(function) + template + struct ScopedRemove { + ScopedRemove(VariantMap& map, const TKey& key) : map_(map), key_(key) {} + ~ScopedRemove() { + map_.Remove(key_); + } + + VariantMap& map_; + const TKey& key_; + }; + + public: + // Release the value from the key. If it was not set in the map, returns the default value. + // If the key was set, it is removed as a side effect. + template + TValue ReleaseOrDefault(const TKey& key) { + ScopedRemove remove_on_return(*this, key); + + TValue* ptr = Get(key); + if (ptr != nullptr) { + return std::move(*ptr); + } else { + TValue default_value = key.CreateDefaultValue(); + return std::move(default_value); + } + } + + // See if a value is stored for this key. + template + bool Exists(const TKey& key) const { + return GetKeyValueIterator(key) != storage_map_.end(); + } + + // Set a value for a given key, overwriting the previous value if any. + template + void Set(const TKey& key, const TValue& value) { + Remove(key); + storage_map_.insert({{key.Clone(), new TValue(value)}}); + } + + // Set a value for a given key, only if there was no previous value before. + // Returns true if the value was set, false if a previous value existed. + template + bool SetIfMissing(const TKey& key, const TValue& value) { + TValue* ptr = Get(key); + if (ptr == nullptr) { + Set(key, value); + return true; + } + return false; + } + + // Remove the value for a given key, or a no-op if there was no previously set value. + template + void Remove(const TKey& key) { + StaticAssertKeyType(); + + auto&& it = GetKeyValueIterator(key); + if (it != storage_map_.end()) { + key.ValueDelete(it->second); + delete it->first; + storage_map_.erase(it); + } + } + + // Remove all key/value pairs. + void Clear() { + DeleteStoredValues(); + storage_map_.clear(); + } + + // How many key/value pairs are stored in this map. + size_t Size() const { + return storage_map_.size(); + } + + // Construct an empty map. + explicit VariantMap() {} + + template + explicit VariantMap(const TKeyValue& ... key_value_list) { + static_assert(sizeof...(TKeyValue) % 2 == 0, "Must be an even number of key/value elements"); + InitializeParameters(key_value_list...); + } + + // Create a new map from an existing map, copying all the key/value pairs. + VariantMap(const VariantMap& other) { + operator=(other); + } + + // Copy the key/value pairs from the other map into this one. Existing key/values are cleared. + VariantMap& operator=(const VariantMap& other) { + if (this == &other) { + return *this; + } + + Clear(); + + for (auto&& kv_pair : other.storage_map_) { + const detail::VariantMapKeyRaw* raw_key_other = kv_pair.first; + void* value = kv_pair.second; + + detail::VariantMapKeyRaw* cloned_raw_key = raw_key_other->Clone(); + void* cloned_value = raw_key_other->ValueClone(value); + + storage_map_.insert({{ cloned_raw_key, cloned_value }}); + } + + return *this; + } + + // Create a new map by moving an existing map into this one. The other map becomes empty. + VariantMap(VariantMap&& other) { + operator=(std::forward(other)); + } + + // Move the existing map's key/value pairs into this one. The other map becomes empty. + VariantMap& operator=(VariantMap&& other) { + if (this != &other) { + Clear(); + storage_map_.swap(other.storage_map_); + other.storage_map_.clear(); + } + return *this; + } + + ~VariantMap() { + DeleteStoredValues(); + } + + private: + void InitializeParameters() {} + + template + void InitializeParameters(const TK& key, const TValue& value, const Rest& ... rest) { + static_assert( + std::is_same>::value, "The 0th/2nd/4th/etc parameters must be a key"); + + const TKey& key_refined = key; + + Set(key_refined, value); + InitializeParameters(rest...); + } + + // Custom key comparator for std::map, needed since we are storing raw pointers as the keys. + struct KeyComparator { + bool operator()(const detail::VariantMapKeyRaw* lhs, + const detail::VariantMapKeyRaw* rhs) const { + if (lhs == nullptr) { + return lhs != rhs; + } + + return lhs->Compare(rhs); + } + }; + + // Map of key pointers to value pointers. Pointers are never null. + using StorageMap = std::map; + + template + typename StorageMap::iterator GetKeyValueIterator(const TKey& key) { + StaticAssertKeyType(); + + const TKey* key_ptr = &key; + const detail::VariantMapKeyRaw* raw_ptr = key_ptr; + return storage_map_.find(raw_ptr); + } + + template + typename StorageMap::const_iterator GetKeyValueIterator(const TKey& key) const { + StaticAssertKeyType(); + + const TKey* key_ptr = &key; + const detail::VariantMapKeyRaw* raw_ptr = key_ptr; + return storage_map_.find(raw_ptr); + } + + template + TValue* GetValuePtr(const TKey& key) { + return const_cast(GetValueConstPtr(key)); + } + + template + const TValue* GetValuePtr(const TKey& key) const { + return GetValueConstPtr(key); + } + + template + const TValue* GetValueConstPtr(const TKey& key) const { + auto&& it = GetKeyValueIterator(key); + if (it == storage_map_.end()) { + return nullptr; + } + + return reinterpret_cast(it->second); + } + + template + static void StaticAssertKeyType() { + static_assert(std::is_base_of, TKey>::value, + "The provided key type (TKey) must be a subclass of VariantMapKey"); + } + + void DeleteStoredValues() { + for (auto&& kv_pair : storage_map_) { + kv_pair.first->ValueDelete(kv_pair.second); + delete kv_pair.first; + } + } + + StorageMap storage_map_; +}; + +} // namespace art + +#endif // ART_RUNTIME_BASE_VARIANT_MAP_H_ diff --git a/runtime/base/variant_map_test.cc b/runtime/base/variant_map_test.cc new file mode 100644 index 000000000..827de4624 --- /dev/null +++ b/runtime/base/variant_map_test.cc @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "variant_map.h" +#include "gtest/gtest.h" + +#define EXPECT_NULL(expected) EXPECT_EQ(reinterpret_cast(expected), \ + reinterpret_cast(NULL)); + +namespace art { + +namespace { + template + struct FruitMapKey : VariantMapKey { + FruitMapKey() {} + }; + + struct FruitMap : VariantMap { + // This 'using' line is necessary to inherit the variadic constructor. + using VariantMap::VariantMap; + + // Make the next '4' usages of Key slightly shorter to type. + template + using Key = FruitMapKey; + + static const Key Apple; + static const Key Orange; + }; + + const FruitMap::Key FruitMap::Apple; + const FruitMap::Key FruitMap::Orange; +} // namespace + +TEST(VariantMaps, BasicReadWrite) { + FruitMap fm; + + EXPECT_NULL(fm.Get(FruitMap::Apple)); + EXPECT_FALSE(fm.Exists(FruitMap::Apple)); + EXPECT_NULL(fm.Get(FruitMap::Orange)); + EXPECT_FALSE(fm.Exists(FruitMap::Orange)); + + fm.Set(FruitMap::Apple, 1); + EXPECT_NULL(fm.Get(FruitMap::Orange)); + EXPECT_EQ(1, *fm.Get(FruitMap::Apple)); + EXPECT_TRUE(fm.Exists(FruitMap::Apple)); + + fm.Set(FruitMap::Apple, 5); + EXPECT_NULL(fm.Get(FruitMap::Orange)); + EXPECT_EQ(5, *fm.Get(FruitMap::Apple)); + EXPECT_TRUE(fm.Exists(FruitMap::Apple)); + + fm.Set(FruitMap::Orange, 555.0); + EXPECT_EQ(5, *fm.Get(FruitMap::Apple)); + EXPECT_DOUBLE_EQ(555.0, *fm.Get(FruitMap::Orange)); + EXPECT_EQ(size_t(2), fm.Size()); + + fm.Remove(FruitMap::Apple); + EXPECT_FALSE(fm.Exists(FruitMap::Apple)); + + fm.Clear(); + EXPECT_EQ(size_t(0), fm.Size()); + EXPECT_FALSE(fm.Exists(FruitMap::Orange)); +} + +TEST(VariantMaps, RuleOfFive) { + // Test empty constructor + FruitMap fmEmpty; + EXPECT_EQ(size_t(0), fmEmpty.Size()); + + // Test empty constructor + FruitMap fmFilled; + fmFilled.Set(FruitMap::Apple, 1); + fmFilled.Set(FruitMap::Orange, 555.0); + EXPECT_EQ(size_t(2), fmFilled.Size()); + + // Test copy constructor + FruitMap fmEmptyCopy(fmEmpty); + EXPECT_EQ(size_t(0), fmEmptyCopy.Size()); + + // Test copy constructor + FruitMap fmFilledCopy(fmFilled); + EXPECT_EQ(size_t(2), fmFilledCopy.Size()); + EXPECT_EQ(*fmFilled.Get(FruitMap::Apple), *fmFilledCopy.Get(FruitMap::Apple)); + EXPECT_DOUBLE_EQ(*fmFilled.Get(FruitMap::Orange), *fmFilledCopy.Get(FruitMap::Orange)); + + // Test operator= + FruitMap fmFilledCopy2; + fmFilledCopy2 = fmFilled; + EXPECT_EQ(size_t(2), fmFilledCopy2.Size()); + EXPECT_EQ(*fmFilled.Get(FruitMap::Apple), *fmFilledCopy2.Get(FruitMap::Apple)); + EXPECT_DOUBLE_EQ(*fmFilled.Get(FruitMap::Orange), *fmFilledCopy2.Get(FruitMap::Orange)); + + // Test move constructor + FruitMap fmMoved(std::move(fmFilledCopy)); + EXPECT_EQ(size_t(0), fmFilledCopy.Size()); + EXPECT_EQ(size_t(2), fmMoved.Size()); + EXPECT_EQ(*fmFilled.Get(FruitMap::Apple), *fmMoved.Get(FruitMap::Apple)); + EXPECT_DOUBLE_EQ(*fmFilled.Get(FruitMap::Orange), *fmMoved.Get(FruitMap::Orange)); + + // Test operator= move + FruitMap fmMoved2; + fmMoved2.Set(FruitMap::Apple, 12345); // This value will be clobbered after the move + + fmMoved2 = std::move(fmFilledCopy2); + EXPECT_EQ(size_t(0), fmFilledCopy2.Size()); + EXPECT_EQ(size_t(2), fmMoved2.Size()); + EXPECT_EQ(*fmFilled.Get(FruitMap::Apple), *fmMoved2.Get(FruitMap::Apple)); + EXPECT_DOUBLE_EQ(*fmFilled.Get(FruitMap::Orange), *fmMoved2.Get(FruitMap::Orange)); +} + +TEST(VariantMaps, VariadicConstructors) { + // Variadic constructor, 1 kv/pair + FruitMap fmApple(FruitMap::Apple, 12345); + EXPECT_EQ(size_t(1), fmApple.Size()); + EXPECT_EQ(12345, *fmApple.Get(FruitMap::Apple)); + + // Variadic constructor, 2 kv/pair + FruitMap fmAppleAndOrange(FruitMap::Apple, 12345, + FruitMap::Orange, 100.0); + EXPECT_EQ(size_t(2), fmAppleAndOrange.Size()); + EXPECT_EQ(12345, *fmAppleAndOrange.Get(FruitMap::Apple)); + EXPECT_DOUBLE_EQ(100.0, *fmAppleAndOrange.Get(FruitMap::Orange)); +} + +TEST(VariantMaps, ReleaseOrDefault) { + FruitMap fmAppleAndOrange(FruitMap::Apple, 12345, + FruitMap::Orange, 100.0); + + int apple = fmAppleAndOrange.ReleaseOrDefault(FruitMap::Apple); + EXPECT_EQ(12345, apple); + + // Releasing will also remove the Apple key. + EXPECT_EQ(size_t(1), fmAppleAndOrange.Size()); + + // Releasing again yields a default value. + int apple2 = fmAppleAndOrange.ReleaseOrDefault(FruitMap::Apple); + EXPECT_EQ(0, apple2); +} + +TEST(VariantMaps, GetOrDefault) { + FruitMap fm(FruitMap::Apple, 12345); + + // Apple gives the expected value we set. + int apple = fm.GetOrDefault(FruitMap::Apple); + EXPECT_EQ(12345, apple); + + // Map is still 1. + EXPECT_EQ(size_t(1), fm.Size()); + + // Orange gives back a default value, since it's not in the map. + double orange = fm.GetOrDefault(FruitMap::Orange); + EXPECT_DOUBLE_EQ(0.0, orange); +} + +} // namespace art diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index d48ac9d6b..b7ffd609b 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -224,6 +224,7 @@ void CommonRuntimeTest::SetUp() { options.push_back(std::make_pair(max_heap_string, nullptr)); options.push_back(std::make_pair("compilercallbacks", callbacks_.get())); SetUpRuntimeOptions(&options); + if (!Runtime::Create(options, false)) { LOG(FATAL) << "Failed to create runtime"; return; diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h index 38a973330..9efea8474 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -76,6 +76,9 @@ class CommonRuntimeTest : public testing::Test { CommonRuntimeTest(); ~CommonRuntimeTest(); + // Gets the path of the libcore dex file. + static std::string GetLibCoreDexFileName(); + protected: static bool IsHost() { return !kIsTargetBuild; @@ -98,11 +101,8 @@ class CommonRuntimeTest : public testing::Test { virtual void TearDown(); - // Gets the path of the libcore dex file. - std::string GetLibCoreDexFileName(); - // Gets the path of the specified dex file for host or target. - std::string GetDexFileName(const std::string& jar_prefix); + static std::string GetDexFileName(const std::string& jar_prefix); std::string GetTestAndroidRoot(); diff --git a/runtime/gc/collector_type.h b/runtime/gc/collector_type.h index ef5d56ec9..9c620977c 100644 --- a/runtime/gc/collector_type.h +++ b/runtime/gc/collector_type.h @@ -46,6 +46,19 @@ enum CollectorType { }; std::ostream& operator<<(std::ostream& os, const CollectorType& collector_type); +static constexpr CollectorType kCollectorTypeDefault = +#if ART_DEFAULT_GC_TYPE_IS_CMS + kCollectorTypeCMS +#elif ART_DEFAULT_GC_TYPE_IS_SS + kCollectorTypeSS +#elif ART_DEFAULT_GC_TYPE_IS_GSS + kCollectorTypeGSS +#else + gc::kCollectorTypeCMS +#error "ART default GC type must be set" +#endif + ; // NOLINT [whitespace/semicolon] [5] + } // namespace gc } // namespace art diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 5a60c87f5..dc425104d 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -364,11 +364,11 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max CHECK(non_moving_space_ != nullptr); CHECK(!non_moving_space_->CanMoveObjects()); // Allocate the large object space. - if (large_object_space_type == space::kLargeObjectSpaceTypeFreeList) { + if (large_object_space_type == space::LargeObjectSpaceType::kFreeList) { large_object_space_ = space::FreeListSpace::Create("free list large object space", nullptr, capacity_); CHECK(large_object_space_ != nullptr) << "Failed to create large object space"; - } else if (large_object_space_type == space::kLargeObjectSpaceTypeMap) { + } else if (large_object_space_type == space::LargeObjectSpaceType::kMap) { large_object_space_ = space::LargeObjectMapSpace::Create("mem map large object space"); CHECK(large_object_space_ != nullptr) << "Failed to create large object space"; } else { diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index 9aced81a7..0beb20c93 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -152,7 +152,7 @@ class Heap { space::kLargeObjectSpaceTypeFreeList; #else static constexpr space::LargeObjectSpaceType kDefaultLargeObjectSpaceType = - space::kLargeObjectSpaceTypeMap; + space::LargeObjectSpaceType::kMap; #endif // Used so that we don't overflow the allocation time atomic integer. static constexpr size_t kTimeAdjust = 1024; diff --git a/runtime/gc/space/large_object_space.h b/runtime/gc/space/large_object_space.h index 850a0066c..847f57581 100644 --- a/runtime/gc/space/large_object_space.h +++ b/runtime/gc/space/large_object_space.h @@ -31,10 +31,10 @@ namespace space { class AllocationInfo; -enum LargeObjectSpaceType { - kLargeObjectSpaceTypeDisabled, - kLargeObjectSpaceTypeMap, - kLargeObjectSpaceTypeFreeList, +enum class LargeObjectSpaceType { + kDisabled, + kMap, + kFreeList, }; // Abstraction implemented by all large object spaces. diff --git a/runtime/globals.h b/runtime/globals.h index 0756a73a8..084547558 100644 --- a/runtime/globals.h +++ b/runtime/globals.h @@ -110,16 +110,16 @@ static constexpr bool kPoisonHeapReferences = false; #endif // Kinds of tracing clocks. -enum TraceClockSource { - kTraceClockSourceThreadCpu, - kTraceClockSourceWall, - kTraceClockSourceDual, // Both wall and thread CPU clocks. +enum class TraceClockSource { + kThreadCpu, + kWall, + kDual, // Both wall and thread CPU clocks. }; #if defined(__linux__) -static constexpr TraceClockSource kDefaultTraceClockSource = kTraceClockSourceDual; +static constexpr TraceClockSource kDefaultTraceClockSource = TraceClockSource::kDual; #else -static constexpr TraceClockSource kDefaultTraceClockSource = kTraceClockSourceWall; +static constexpr TraceClockSource kDefaultTraceClockSource = TraceClockSource::kWall; #endif static constexpr bool kDefaultMustRelocate = true; diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc index 40417d850..ea7c1927c 100644 --- a/runtime/java_vm_ext.cc +++ b/runtime/java_vm_ext.cc @@ -32,6 +32,7 @@ #include "java_vm_ext.h" #include "parsed_options.h" #include "runtime-inl.h" +#include "runtime_options.h" #include "ScopedLocalRef.h" #include "scoped_thread_state_change.h" #include "thread-inl.h" @@ -357,14 +358,15 @@ const JNIInvokeInterface gJniInvokeInterface = { JII::AttachCurrentThreadAsDaemon }; -JavaVMExt::JavaVMExt(Runtime* runtime, ParsedOptions* options) +JavaVMExt::JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options) : runtime_(runtime), check_jni_abort_hook_(nullptr), check_jni_abort_hook_data_(nullptr), check_jni_(false), // Initialized properly in the constructor body below. - force_copy_(options->force_copy_), - tracing_enabled_(!options->jni_trace_.empty() || VLOG_IS_ON(third_party_jni)), - trace_(options->jni_trace_), + force_copy_(runtime_options.Exists(RuntimeArgumentMap::JniOptsForceCopy)), + tracing_enabled_(runtime_options.Exists(RuntimeArgumentMap::JniTrace) + || VLOG_IS_ON(third_party_jni)), + trace_(runtime_options.GetOrDefault(RuntimeArgumentMap::JniTrace)), globals_lock_("JNI global reference table lock"), globals_(gGlobalsInitial, gGlobalsMax, kGlobal), libraries_(new Libraries), @@ -374,9 +376,7 @@ JavaVMExt::JavaVMExt(Runtime* runtime, ParsedOptions* options) allow_new_weak_globals_(true), weak_globals_add_condition_("weak globals add condition", weak_globals_lock_) { functions = unchecked_functions_; - if (options->check_jni_) { - SetCheckJniEnabled(true); - } + SetCheckJniEnabled(runtime_options.Exists(RuntimeArgumentMap::CheckJni)); } JavaVMExt::~JavaVMExt() { diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h index c3f0a82e6..037fbe5f4 100644 --- a/runtime/java_vm_ext.h +++ b/runtime/java_vm_ext.h @@ -34,10 +34,11 @@ namespace mirror { class Libraries; class ParsedOptions; class Runtime; +struct RuntimeArgumentMap; class JavaVMExt : public JavaVM { public: - JavaVMExt(Runtime* runtime, ParsedOptions* options); + JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options); ~JavaVMExt(); bool ForceCopy() const { diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h index 9309ab5c5..6464a623c 100644 --- a/runtime/jdwp/jdwp.h +++ b/runtime/jdwp/jdwp.h @@ -108,6 +108,8 @@ struct JdwpOptions { uint16_t port; }; +bool operator==(const JdwpOptions& lhs, const JdwpOptions& rhs); + struct JdwpEvent; class JdwpNetStateBase; struct ModBasket; diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc index 40211dea1..b04aa6e13 100644 --- a/runtime/jdwp/jdwp_main.cc +++ b/runtime/jdwp/jdwp_main.cc @@ -619,6 +619,18 @@ bool operator!=(const JdwpLocation& lhs, const JdwpLocation& rhs) { return !(lhs == rhs); } +bool operator==(const JdwpOptions& lhs, const JdwpOptions& rhs) { + if (&lhs == &rhs) { + return true; + } + + return lhs.transport == rhs.transport && + lhs.server == rhs.server && + lhs.suspend == rhs.suspend && + lhs.host == rhs.host && + lhs.port == rhs.port; +} + } // namespace JDWP } // namespace art diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 96430a053..b27bd6b23 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -26,688 +26,450 @@ #include "trace.h" #include "utils.h" +#include "cmdline_parser.h" +#include "runtime_options.h" + namespace art { +using MemoryKiB = Memory<1024>; + ParsedOptions::ParsedOptions() - : - check_jni_(kIsDebugBuild), // -Xcheck:jni is off by default for regular - // builds but on by default in debug builds. - force_copy_(false), - compiler_callbacks_(nullptr), - is_zygote_(false), - must_relocate_(kDefaultMustRelocate), - dex2oat_enabled_(true), - image_dex2oat_enabled_(true), - interpreter_only_(kPoisonHeapReferences), // kPoisonHeapReferences currently works with - // the interpreter only. - // TODO: make it work with the compiler. - is_explicit_gc_disabled_(false), - use_tlab_(false), - verify_pre_gc_heap_(false), - verify_pre_sweeping_heap_(kIsDebugBuild), // Pre sweeping is the one that usually fails - // if the GC corrupted the heap. - verify_post_gc_heap_(false), - verify_pre_gc_rosalloc_(kIsDebugBuild), - verify_pre_sweeping_rosalloc_(false), - verify_post_gc_rosalloc_(false), - long_pause_log_threshold_(gc::Heap::kDefaultLongPauseLogThreshold), - long_gc_log_threshold_(gc::Heap::kDefaultLongGCLogThreshold), - dump_gc_performance_on_shutdown_(false), - ignore_max_footprint_(false), - heap_initial_size_(gc::Heap::kDefaultInitialSize), - heap_maximum_size_(gc::Heap::kDefaultMaximumSize), - heap_growth_limit_(0), // 0 means no growth limit. - heap_min_free_(gc::Heap::kDefaultMinFree), - heap_max_free_(gc::Heap::kDefaultMaxFree), - heap_non_moving_space_capacity_(gc::Heap::kDefaultNonMovingSpaceCapacity), - large_object_space_type_(gc::Heap::kDefaultLargeObjectSpaceType), - large_object_threshold_(gc::Heap::kDefaultLargeObjectThreshold), - heap_target_utilization_(gc::Heap::kDefaultTargetUtilization), - foreground_heap_growth_multiplier_(gc::Heap::kDefaultHeapGrowthMultiplier), - parallel_gc_threads_(1), - conc_gc_threads_(0), // Only the main GC thread, no workers. - collector_type_( // The default GC type is set in makefiles. -#if ART_DEFAULT_GC_TYPE_IS_CMS - gc::kCollectorTypeCMS), -#elif ART_DEFAULT_GC_TYPE_IS_SS - gc::kCollectorTypeSS), -#elif ART_DEFAULT_GC_TYPE_IS_GSS - gc::kCollectorTypeGSS), -#else - gc::kCollectorTypeCMS), -#error "ART default GC type must be set" -#endif - background_collector_type_(gc::kCollectorTypeNone), - // If background_collector_type_ is - // kCollectorTypeNone, it defaults to the - // collector_type_ after parsing options. If - // you set this to kCollectorTypeHSpaceCompact - // then we will do an hspace compaction when - // we transition to background instead of a - // normal collector transition. - stack_size_(0), // 0 means default. - max_spins_before_thin_lock_inflation_(Monitor::kDefaultMaxSpinsBeforeThinLockInflation), - low_memory_mode_(false), - lock_profiling_threshold_(0), - method_trace_(false), - method_trace_file_("/data/method-trace-file.bin"), - method_trace_file_size_(10 * MB), - hook_is_sensitive_thread_(nullptr), + : hook_is_sensitive_thread_(nullptr), hook_vfprintf_(vfprintf), hook_exit_(exit), - hook_abort_(nullptr), // We don't call abort(3) by default; see - // Runtime::Abort. - profile_clock_source_(kDefaultTraceClockSource), - verify_(true), - image_isa_(kRuntimeISA), - use_homogeneous_space_compaction_for_oom_(true), // Enable hspace compaction on OOM by default. - min_interval_homogeneous_space_compaction_by_oom_(MsToNs(100 * 1000)) { // 100s. - if (kUseReadBarrier) { - // If RB is enabled (currently a build-time decision), use CC as the default GC. - collector_type_ = gc::kCollectorTypeCC; - background_collector_type_ = gc::kCollectorTypeCC; // Disable background compaction for CC. - interpreter_only_ = true; // Disable the compiler for CC (for now). - // use_tlab_ = true; - } + hook_abort_(nullptr) { // We don't call abort(3) by default; see + // Runtime::Abort } -ParsedOptions* ParsedOptions::Create(const RuntimeOptions& options, bool ignore_unrecognized) { +ParsedOptions* ParsedOptions::Create(const RuntimeOptions& options, bool ignore_unrecognized, + RuntimeArgumentMap* runtime_options) { + CHECK(runtime_options != nullptr); + std::unique_ptr parsed(new ParsedOptions()); - if (parsed->Parse(options, ignore_unrecognized)) { + if (parsed->Parse(options, ignore_unrecognized, runtime_options)) { return parsed.release(); } return nullptr; } -// Parse a string of the form /[0-9]+[kKmMgG]?/, which is used to specify -// memory sizes. [kK] indicates kilobytes, [mM] megabytes, and -// [gG] gigabytes. -// -// "s" should point just past the "-Xm?" part of the string. -// "div" specifies a divisor, e.g. 1024 if the value must be a multiple -// of 1024. -// -// The spec says the -Xmx and -Xms options must be multiples of 1024. It -// doesn't say anything about -Xss. -// -// Returns 0 (a useless size) if "s" is malformed or specifies a low or -// non-evenly-divisible value. -// -size_t ParseMemoryOption(const char* s, size_t div) { - // strtoul accepts a leading [+-], which we don't want, - // so make sure our string starts with a decimal digit. - if (isdigit(*s)) { - char* s2; - size_t val = strtoul(s, &s2, 10); - if (s2 != s) { - // s2 should be pointing just after the number. - // If this is the end of the string, the user - // has specified a number of bytes. Otherwise, - // there should be exactly one more character - // that specifies a multiplier. - if (*s2 != '\0') { - // The remainder of the string is either a single multiplier - // character, or nothing to indicate that the value is in - // bytes. - char c = *s2++; - if (*s2 == '\0') { - size_t mul; - if (c == '\0') { - mul = 1; - } else if (c == 'k' || c == 'K') { - mul = KB; - } else if (c == 'm' || c == 'M') { - mul = MB; - } else if (c == 'g' || c == 'G') { - mul = GB; - } else { - // Unknown multiplier character. - return 0; - } - - if (val <= std::numeric_limits::max() / mul) { - val *= mul; - } else { - // Clamp to a multiple of 1024. - val = std::numeric_limits::max() & ~(1024-1); - } - } else { - // There's more than one character after the numeric part. - return 0; - } - } - // The man page says that a -Xm value must be a multiple of 1024. - if (val % div == 0) { - return val; - } - } - } - return 0; +using RuntimeParser = CmdlineParser; + +// Yes, the stack frame is huge. But we get called super early on (and just once) +// to pass the command line arguments, so we'll probably be ok. +// Ideas to avoid suppressing this diagnostic are welcome! +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wframe-larger-than=" + +std::unique_ptr ParsedOptions::MakeParser(bool ignore_unrecognized) { + using M = RuntimeArgumentMap; + + std::unique_ptr parser_builder = + std::unique_ptr(new RuntimeParser::Builder()); + + parser_builder-> + Define("-Xzygote") + .IntoKey(M::Zygote) + .Define("-help") + .IntoKey(M::Help) + .Define("-showversion") + .IntoKey(M::ShowVersion) + .Define("-Xbootclasspath:_") + .WithType() + .IntoKey(M::BootClassPath) + .Define("-Xbootclasspath-locations:_") + .WithType>() // std::vector, split by : + .IntoKey(M::BootClassPathLocations) + .Define({"-classpath _", "-cp _"}) + .WithType() + .IntoKey(M::ClassPath) + .Define("-Ximage:_") + .WithType() + .IntoKey(M::Image) + .Define("-Xcheck:jni") + .IntoKey(M::CheckJni) + .Define("-Xjniopts:forcecopy") + .IntoKey(M::JniOptsForceCopy) + .Define({"-Xrunjdwp:_", "-agentlib:jdwp=_"}) + .WithType() + .IntoKey(M::JdwpOptions) + .Define("-Xms_") + .WithType() + .IntoKey(M::MemoryInitialSize) + .Define("-Xmx_") + .WithType() + .IntoKey(M::MemoryMaximumSize) + .Define("-XX:HeapGrowthLimit=_") + .WithType() + .IntoKey(M::HeapGrowthLimit) + .Define("-XX:HeapMinFree=_") + .WithType() + .IntoKey(M::HeapMinFree) + .Define("-XX:HeapMaxFree=_") + .WithType() + .IntoKey(M::HeapMaxFree) + .Define("-XX:NonMovingSpaceCapacity=_") + .WithType() + .IntoKey(M::NonMovingSpaceCapacity) + .Define("-XX:HeapTargetUtilization=_") + .WithType().WithRange(0.1, 0.9) + .IntoKey(M::HeapTargetUtilization) + .Define("-XX:ForegroundHeapGrowthMultiplier=_") + .WithType().WithRange(0.1, 1.0) + .IntoKey(M::ForegroundHeapGrowthMultiplier) + .Define("-XX:ParallelGCThreads=_") + .WithType() + .IntoKey(M::ParallelGCThreads) + .Define("-XX:ConcGCThreads=_") + .WithType() + .IntoKey(M::ConcGCThreads) + .Define("-Xss_") + .WithType>() + .IntoKey(M::StackSize) + .Define("-XX:MaxSpinsBeforeThinLockInflation=_") + .WithType() + .IntoKey(M::MaxSpinsBeforeThinLockInflation) + .Define("-XX:LongPauseLogThreshold=_") // in ms + .WithType() // store as ns + .IntoKey(M::LongPauseLogThreshold) + .Define("-XX:LongGCLogThreshold=_") // in ms + .WithType() // store as ns + .IntoKey(M::LongGCLogThreshold) + .Define("-XX:DumpGCPerformanceOnShutdown") + .IntoKey(M::DumpGCPerformanceOnShutdown) + .Define("-XX:IgnoreMaxFootprint") + .IntoKey(M::IgnoreMaxFootprint) + .Define("-XX:LowMemoryMode") + .IntoKey(M::LowMemoryMode) + .Define("-XX:UseTLAB") + .IntoKey(M::UseTLAB) + .Define({"-XX:EnableHSpaceCompactForOOM", "-XX:DisableHSpaceCompactForOOM"}) + .WithValues({true, false}) + .IntoKey(M::EnableHSpaceCompactForOOM) + .Define("-XX:HspaceCompactForOOMMinIntervalMs=_") // in ms + .WithType() // store as ns + .IntoKey(M::HSpaceCompactForOOMMinIntervalsMs) + .Define("-D_") + .WithType>().AppendValues() + .IntoKey(M::PropertiesList) + .Define("-Xjnitrace:_") + .WithType() + .IntoKey(M::JniTrace) + .Define("-Xpatchoat:_") + .WithType() + .IntoKey(M::PatchOat) + .Define({"-Xrelocate", "-Xnorelocate"}) + .WithValues({true, false}) + .IntoKey(M::Relocate) + .Define({"-Xdex2oat", "-Xnodex2oat"}) + .WithValues({true, false}) + .IntoKey(M::Dex2Oat) + .Define({"-Ximage-dex2oat", "-Xnoimage-dex2oat"}) + .WithValues({true, false}) + .IntoKey(M::ImageDex2Oat) + .Define("-Xint") + .WithValue(true) + .IntoKey(M::Interpret) + .Define("-Xgc:_") + .WithType() + .IntoKey(M::GcOption) + .Define("-XX:LargeObjectSpace=_") + .WithType() + .WithValueMap({{"disabled", gc::space::LargeObjectSpaceType::kDisabled}, + {"freelist", gc::space::LargeObjectSpaceType::kFreeList}, + {"map", gc::space::LargeObjectSpaceType::kMap}}) + .IntoKey(M::LargeObjectSpace) + .Define("-XX:LargeObjectThreshold=_") + .WithType>() + .IntoKey(M::LargeObjectThreshold) + .Define("-XX:BackgroundGC=_") + .WithType() + .IntoKey(M::BackgroundGc) + .Define("-XX:+DisableExplicitGC") + .IntoKey(M::DisableExplicitGC) + .Define("-verbose:_") + .WithType() + .IntoKey(M::Verbose) + .Define("-Xlockprofthreshold:_") + .WithType() + .IntoKey(M::LockProfThreshold) + .Define("-Xstacktracefile:_") + .WithType() + .IntoKey(M::StackTraceFile) + .Define("-Xmethod-trace") + .IntoKey(M::MethodTrace) + .Define("-Xmethod-trace-file:_") + .WithType() + .IntoKey(M::MethodTraceFile) + .Define("-Xmethod-trace-file-size:_") + .WithType() + .IntoKey(M::MethodTraceFileSize) + .Define("-Xprofile:_") + .WithType() + .WithValueMap({{"threadcpuclock", TraceClockSource::kThreadCpu}, + {"wallclock", TraceClockSource::kWall}, + {"dualclock", TraceClockSource::kDual}}) + .IntoKey(M::ProfileClock) + .Define("-Xenable-profiler") + .WithType() + .AppendValues() + .IntoKey(M::ProfilerOpts) // NOTE: Appends into same key as -Xprofile-* + .Define("-Xprofile-_") // -Xprofile-: + .WithType() + .AppendValues() + .IntoKey(M::ProfilerOpts) // NOTE: Appends into same key as -Xenable-profiler + .Define("-Xcompiler:_") + .WithType() + .IntoKey(M::Compiler) + .Define("-Xcompiler-option _") + .WithType>() + .AppendValues() + .IntoKey(M::CompilerOptions) + .Define("-Ximage-compiler-option _") + .WithType>() + .AppendValues() + .IntoKey(M::ImageCompilerOptions) + .Define("-Xverify:_") + .WithType() + .WithValueMap({{"none", false}, + {"remote", true}, + {"all", true}}) + .IntoKey(M::Verify) + .Define("-XX:NativeBridge=_") + .WithType() + .IntoKey(M::NativeBridge) + .Ignore({ + "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", + "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", + "-Xdexopt:_", "-Xnoquithandler", "-Xjnigreflimit:_", "-Xgenregmap", "-Xnogenregmap", + "-Xverifyopt:_", "-Xcheckdexsum", "-Xincludeselectedop", "-Xjitop:_", + "-Xincludeselectedmethod", "-Xjitthreshold:_", "-Xjitcodecachesize:_", + "-Xjitblocking", "-Xjitmethod:_", "-Xjitclass:_", "-Xjitoffset:_", + "-Xjitconfig:_", "-Xjitcheckcg", "-Xjitverbose", "-Xjitprofile", + "-Xjitdisableopt", "-Xjitsuspendpoll", "-XX:mainThreadStackSize=_"}) + .IgnoreUnrecognized(ignore_unrecognized); + + // TODO: Move Usage information into this DSL. + + return std::unique_ptr(new RuntimeParser(parser_builder->Build())); } -static gc::CollectorType ParseCollectorType(const std::string& option) { - if (option == "MS" || option == "nonconcurrent") { - return gc::kCollectorTypeMS; - } else if (option == "CMS" || option == "concurrent") { - return gc::kCollectorTypeCMS; - } else if (option == "SS") { - return gc::kCollectorTypeSS; - } else if (option == "GSS") { - return gc::kCollectorTypeGSS; - } else if (option == "CC") { - return gc::kCollectorTypeCC; - } else if (option == "MC") { - return gc::kCollectorTypeMC; - } else { - return gc::kCollectorTypeNone; - } -} +#pragma GCC diagnostic pop -bool ParsedOptions::ParseXGcOption(const std::string& option) { - std::vector gc_options; - Split(option.substr(strlen("-Xgc:")), ',', &gc_options); - for (const std::string& gc_option : gc_options) { - gc::CollectorType collector_type = ParseCollectorType(gc_option); - if (collector_type != gc::kCollectorTypeNone) { - collector_type_ = collector_type; - } else if (gc_option == "preverify") { - verify_pre_gc_heap_ = true; - } else if (gc_option == "nopreverify") { - verify_pre_gc_heap_ = false; - } else if (gc_option == "presweepingverify") { - verify_pre_sweeping_heap_ = true; - } else if (gc_option == "nopresweepingverify") { - verify_pre_sweeping_heap_ = false; - } else if (gc_option == "postverify") { - verify_post_gc_heap_ = true; - } else if (gc_option == "nopostverify") { - verify_post_gc_heap_ = false; - } else if (gc_option == "preverify_rosalloc") { - verify_pre_gc_rosalloc_ = true; - } else if (gc_option == "nopreverify_rosalloc") { - verify_pre_gc_rosalloc_ = false; - } else if (gc_option == "presweepingverify_rosalloc") { - verify_pre_sweeping_rosalloc_ = true; - } else if (gc_option == "nopresweepingverify_rosalloc") { - verify_pre_sweeping_rosalloc_ = false; - } else if (gc_option == "postverify_rosalloc") { - verify_post_gc_rosalloc_ = true; - } else if (gc_option == "nopostverify_rosalloc") { - verify_post_gc_rosalloc_ = false; - } else if ((gc_option == "precise") || - (gc_option == "noprecise") || - (gc_option == "verifycardtable") || - (gc_option == "noverifycardtable")) { - // Ignored for backwards compatibility. - } else { - Usage("Unknown -Xgc option %s\n", gc_option.c_str()); - return false; - } - } - return true; -} - -bool ParsedOptions::Parse(const RuntimeOptions& options, bool ignore_unrecognized) { - const char* boot_class_path_string = getenv("BOOTCLASSPATH"); - if (boot_class_path_string != NULL) { - boot_class_path_string_ = boot_class_path_string; - } - const char* class_path_string = getenv("CLASSPATH"); - if (class_path_string != NULL) { - class_path_string_ = class_path_string; - } - - // Default to number of processors minus one since the main GC thread also does work. - parallel_gc_threads_ = sysconf(_SC_NPROCESSORS_CONF) - 1; - -// gLogVerbosity.class_linker = true; // TODO: don't check this in! -// gLogVerbosity.compiler = true; // TODO: don't check this in! -// gLogVerbosity.gc = true; // TODO: don't check this in! -// gLogVerbosity.heap = true; // TODO: don't check this in! -// gLogVerbosity.jdwp = true; // TODO: don't check this in! -// gLogVerbosity.jni = true; // TODO: don't check this in! -// gLogVerbosity.monitor = true; // TODO: don't check this in! -// gLogVerbosity.profiler = true; // TODO: don't check this in! -// gLogVerbosity.signals = true; // TODO: don't check this in! -// gLogVerbosity.startup = true; // TODO: don't check this in! -// gLogVerbosity.third_party_jni = true; // TODO: don't check this in! -// gLogVerbosity.threads = true; // TODO: don't check this in! -// gLogVerbosity.verifier = true; // TODO: don't check this in! +// Remove all the special options that have something in the void* part of the option. +// If runtime_options is not null, put the options in there. +// As a side-effect, populate the hooks from options. +bool ParsedOptions::ProcessSpecialOptions(const RuntimeOptions& options, + RuntimeArgumentMap* runtime_options, + std::vector* out_options) { + using M = RuntimeArgumentMap; - for (size_t i = 0; i < options.size(); ++i) { - if (true && options[0].first == "-Xzygote") { - LOG(INFO) << "option[" << i << "]=" << options[i].first; - } - } + // TODO: Move the below loop into JNI + // Handle special options that set up hooks for (size_t i = 0; i < options.size(); ++i) { const std::string option(options[i].first); - if (StartsWith(option, "-help")) { - Usage(nullptr); - return false; - } else if (StartsWith(option, "-showversion")) { - UsageMessage(stdout, "ART version %s\n", Runtime::GetVersion()); - Exit(0); - } else if (StartsWith(option, "-Xbootclasspath:")) { - boot_class_path_string_ = option.substr(strlen("-Xbootclasspath:")).data(); - LOG(INFO) << "setting boot class path to " << boot_class_path_string_; - } else if (StartsWith(option, "-Xbootclasspath-locations:")) { - boot_class_path_locations_string_ = option.substr( - strlen("-Xbootclasspath-locations:")).data(); - } else if (option == "-classpath" || option == "-cp") { // TODO: support -Djava.class.path - i++; - if (i == options.size()) { - Usage("Missing required class path value for %s\n", option.c_str()); - return false; - } - const StringPiece& value = options[i].first; - class_path_string_ = value.data(); - } else if (StartsWith(option, "-Ximage:")) { - if (!ParseStringAfterChar(option, ':', &image_)) { - return false; - } - } else if (StartsWith(option, "-Xcheck:jni")) { - check_jni_ = true; - } else if (StartsWith(option, "-Xjniopts:forcecopy")) { - force_copy_ = true; - } else if (StartsWith(option, "-Xrunjdwp:") || StartsWith(option, "-agentlib:jdwp=")) { - std::string tail(option.substr(option[1] == 'X' ? 10 : 15)); - // TODO: move parsing logic out of Dbg - if (tail == "help" || !Dbg::ParseJdwpOptions(tail)) { - if (tail != "help") { - UsageMessage(stderr, "Failed to parse JDWP option %s\n", tail.c_str()); - } - Usage("Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n" - "Example: -Xrunjdwp:transport=dt_socket,address=localhost:6500,server=n\n"); - return false; - } - } else if (StartsWith(option, "-Xms")) { - size_t size = ParseMemoryOption(option.substr(strlen("-Xms")).c_str(), 1024); - if (size == 0) { - Usage("Failed to parse memory option %s\n", option.c_str()); - return false; - } - heap_initial_size_ = size; - } else if (StartsWith(option, "-Xmx")) { - size_t size = ParseMemoryOption(option.substr(strlen("-Xmx")).c_str(), 1024); - if (size == 0) { - Usage("Failed to parse memory option %s\n", option.c_str()); - return false; - } - heap_maximum_size_ = size; - } else if (StartsWith(option, "-XX:HeapGrowthLimit=")) { - size_t size = ParseMemoryOption(option.substr(strlen("-XX:HeapGrowthLimit=")).c_str(), 1024); - if (size == 0) { - Usage("Failed to parse memory option %s\n", option.c_str()); - return false; - } - heap_growth_limit_ = size; - } else if (StartsWith(option, "-XX:HeapMinFree=")) { - size_t size = ParseMemoryOption(option.substr(strlen("-XX:HeapMinFree=")).c_str(), 1024); - if (size == 0) { - Usage("Failed to parse memory option %s\n", option.c_str()); - return false; - } - heap_min_free_ = size; - } else if (StartsWith(option, "-XX:HeapMaxFree=")) { - size_t size = ParseMemoryOption(option.substr(strlen("-XX:HeapMaxFree=")).c_str(), 1024); - if (size == 0) { - Usage("Failed to parse memory option %s\n", option.c_str()); - return false; - } - heap_max_free_ = size; - } else if (StartsWith(option, "-XX:NonMovingSpaceCapacity=")) { - size_t size = ParseMemoryOption( - option.substr(strlen("-XX:NonMovingSpaceCapacity=")).c_str(), 1024); - if (size == 0) { - Usage("Failed to parse memory option %s\n", option.c_str()); - return false; - } - heap_non_moving_space_capacity_ = size; - } else if (StartsWith(option, "-XX:HeapTargetUtilization=")) { - if (!ParseDouble(option, '=', 0.1, 0.9, &heap_target_utilization_)) { - return false; - } - } else if (StartsWith(option, "-XX:ForegroundHeapGrowthMultiplier=")) { - if (!ParseDouble(option, '=', 0.1, 10.0, &foreground_heap_growth_multiplier_)) { - return false; - } - } else if (StartsWith(option, "-XX:ParallelGCThreads=")) { - if (!ParseUnsignedInteger(option, '=', ¶llel_gc_threads_)) { - return false; - } - } else if (StartsWith(option, "-XX:ConcGCThreads=")) { - if (!ParseUnsignedInteger(option, '=', &conc_gc_threads_)) { - return false; - } - } else if (StartsWith(option, "-Xss")) { - size_t size = ParseMemoryOption(option.substr(strlen("-Xss")).c_str(), 1); - if (size == 0) { - Usage("Failed to parse memory option %s\n", option.c_str()); - return false; - } - stack_size_ = size; - } else if (StartsWith(option, "-XX:MaxSpinsBeforeThinLockInflation=")) { - if (!ParseUnsignedInteger(option, '=', &max_spins_before_thin_lock_inflation_)) { - return false; - } - } else if (StartsWith(option, "-XX:LongPauseLogThreshold=")) { - unsigned int value; - if (!ParseUnsignedInteger(option, '=', &value)) { - return false; - } - long_pause_log_threshold_ = MsToNs(value); - } else if (StartsWith(option, "-XX:LongGCLogThreshold=")) { - unsigned int value; - if (!ParseUnsignedInteger(option, '=', &value)) { - return false; - } - long_gc_log_threshold_ = MsToNs(value); - } else if (option == "-XX:DumpGCPerformanceOnShutdown") { - dump_gc_performance_on_shutdown_ = true; - } else if (option == "-XX:IgnoreMaxFootprint") { - ignore_max_footprint_ = true; - } else if (option == "-XX:LowMemoryMode") { - low_memory_mode_ = true; - // TODO Might want to turn off must_relocate here. - } else if (option == "-XX:UseTLAB") { - use_tlab_ = true; - } else if (option == "-XX:EnableHSpaceCompactForOOM") { - use_homogeneous_space_compaction_for_oom_ = true; - } else if (option == "-XX:DisableHSpaceCompactForOOM") { - use_homogeneous_space_compaction_for_oom_ = false; - } else if (StartsWith(option, "-XX:HspaceCompactForOOMMinIntervalMs=")) { - unsigned int value; - if (!ParseUnsignedInteger(option, '=', &value)) { - return false; + if (option == "bootclasspath") { + auto boot_class_path + = reinterpret_cast*>(options[i].second); + + if (runtime_options != nullptr) { + runtime_options->Set(M::BootClassPathDexList, boot_class_path); } - min_interval_homogeneous_space_compaction_by_oom_ = MsToNs(value); - } else if (StartsWith(option, "-D")) { - properties_.push_back(option.substr(strlen("-D"))); - } else if (StartsWith(option, "-Xjnitrace:")) { - jni_trace_ = option.substr(strlen("-Xjnitrace:")); } else if (option == "compilercallbacks") { - compiler_callbacks_ = + CompilerCallbacks* compiler_callbacks = reinterpret_cast(const_cast(options[i].second)); + if (runtime_options != nullptr) { + runtime_options->Set(M::CompilerCallbacksPtr, compiler_callbacks); + } } else if (option == "imageinstructionset") { const char* isa_str = reinterpret_cast(options[i].second); - image_isa_ = GetInstructionSetFromString(isa_str); - if (image_isa_ == kNone) { + auto&& image_isa = GetInstructionSetFromString(isa_str); + if (image_isa == kNone) { Usage("%s is not a valid instruction set.", isa_str); return false; } - } else if (option == "-Xzygote") { - is_zygote_ = true; - } else if (StartsWith(option, "-Xpatchoat:")) { - if (!ParseStringAfterChar(option, ':', &patchoat_executable_)) { - return false; - } - } else if (option == "-Xrelocate") { - must_relocate_ = true; - } else if (option == "-Xnorelocate") { - must_relocate_ = false; - } else if (option == "-Xnodex2oat") { - dex2oat_enabled_ = false; - } else if (option == "-Xdex2oat") { - dex2oat_enabled_ = true; - } else if (option == "-Xnoimage-dex2oat") { - image_dex2oat_enabled_ = false; - } else if (option == "-Ximage-dex2oat") { - image_dex2oat_enabled_ = true; - } else if (option == "-Xint") { - interpreter_only_ = true; - } else if (StartsWith(option, "-Xgc:")) { - if (!ParseXGcOption(option)) { - return false; - } - } else if (StartsWith(option, "-XX:LargeObjectSpace=")) { - std::string substring; - if (!ParseStringAfterChar(option, '=', &substring)) { - return false; - } - if (substring == "disabled") { - large_object_space_type_ = gc::space::kLargeObjectSpaceTypeDisabled; - } else if (substring == "freelist") { - large_object_space_type_ = gc::space::kLargeObjectSpaceTypeFreeList; - } else if (substring == "map") { - large_object_space_type_ = gc::space::kLargeObjectSpaceTypeMap; - } else { - Usage("Unknown -XX:LargeObjectSpace= option %s\n", substring.c_str()); - return false; - } - } else if (StartsWith(option, "-XX:LargeObjectThreshold=")) { - std::string substring; - if (!ParseStringAfterChar(option, '=', &substring)) { - return false; - } - size_t size = ParseMemoryOption(substring.c_str(), 1); - if (size == 0) { - Usage("Failed to parse memory option %s\n", option.c_str()); - return false; - } - large_object_threshold_ = size; - } else if (StartsWith(option, "-XX:BackgroundGC=")) { - std::string substring; - if (!ParseStringAfterChar(option, '=', &substring)) { - return false; - } - // Special handling for HSpaceCompact since this is only valid as a background GC type. - if (substring == "HSpaceCompact") { - background_collector_type_ = gc::kCollectorTypeHomogeneousSpaceCompact; - } else { - gc::CollectorType collector_type = ParseCollectorType(substring); - if (collector_type != gc::kCollectorTypeNone) { - background_collector_type_ = collector_type; - } else { - Usage("Unknown -XX:BackgroundGC option %s\n", substring.c_str()); - return false; - } - } - } else if (option == "-XX:+DisableExplicitGC") { - is_explicit_gc_disabled_ = true; - } else if (StartsWith(option, "-verbose:")) { - std::vector verbose_options; - Split(option.substr(strlen("-verbose:")), ',', &verbose_options); - for (size_t j = 0; j < verbose_options.size(); ++j) { - if (verbose_options[j] == "class") { - gLogVerbosity.class_linker = true; - } else if (verbose_options[j] == "compiler") { - gLogVerbosity.compiler = true; - } else if (verbose_options[j] == "gc") { - gLogVerbosity.gc = true; - } else if (verbose_options[j] == "heap") { - gLogVerbosity.heap = true; - } else if (verbose_options[j] == "jdwp") { - gLogVerbosity.jdwp = true; - } else if (verbose_options[j] == "jni") { - gLogVerbosity.jni = true; - } else if (verbose_options[j] == "monitor") { - gLogVerbosity.monitor = true; - } else if (verbose_options[j] == "profiler") { - gLogVerbosity.profiler = true; - } else if (verbose_options[j] == "signals") { - gLogVerbosity.signals = true; - } else if (verbose_options[j] == "startup") { - gLogVerbosity.startup = true; - } else if (verbose_options[j] == "third-party-jni") { - gLogVerbosity.third_party_jni = true; - } else if (verbose_options[j] == "threads") { - gLogVerbosity.threads = true; - } else if (verbose_options[j] == "verifier") { - gLogVerbosity.verifier = true; - } else { - Usage("Unknown -verbose option %s\n", verbose_options[j].c_str()); - return false; - } - } - } else if (StartsWith(option, "-Xlockprofthreshold:")) { - if (!ParseUnsignedInteger(option, ':', &lock_profiling_threshold_)) { - return false; - } - } else if (StartsWith(option, "-Xstacktracefile:")) { - if (!ParseStringAfterChar(option, ':', &stack_trace_file_)) { - return false; + if (runtime_options != nullptr) { + runtime_options->Set(M::ImageInstructionSet, image_isa); } } else if (option == "sensitiveThread") { const void* hook = options[i].second; - hook_is_sensitive_thread_ = reinterpret_cast(const_cast(hook)); + bool (*hook_is_sensitive_thread)() = reinterpret_cast(const_cast(hook)); + + if (runtime_options != nullptr) { + runtime_options->Set(M::HookIsSensitiveThread, hook_is_sensitive_thread); + } } else if (option == "vfprintf") { const void* hook = options[i].second; if (hook == nullptr) { Usage("vfprintf argument was NULL"); return false; } - hook_vfprintf_ = + int (*hook_vfprintf)(FILE *, const char*, va_list) = reinterpret_cast(const_cast(hook)); + + if (runtime_options != nullptr) { + runtime_options->Set(M::HookVfprintf, hook_vfprintf); + } + hook_vfprintf_ = hook_vfprintf; } else if (option == "exit") { const void* hook = options[i].second; if (hook == nullptr) { Usage("exit argument was NULL"); return false; } - hook_exit_ = reinterpret_cast(const_cast(hook)); + void(*hook_exit)(jint) = reinterpret_cast(const_cast(hook)); + if (runtime_options != nullptr) { + runtime_options->Set(M::HookExit, hook_exit); + } + hook_exit_ = hook_exit; } else if (option == "abort") { const void* hook = options[i].second; if (hook == nullptr) { Usage("abort was NULL\n"); return false; } - hook_abort_ = reinterpret_cast(const_cast(hook)); - } else if (option == "-Xmethod-trace") { - method_trace_ = true; - } else if (StartsWith(option, "-Xmethod-trace-file:")) { - method_trace_file_ = option.substr(strlen("-Xmethod-trace-file:")); - } else if (StartsWith(option, "-Xmethod-trace-file-size:")) { - if (!ParseUnsignedInteger(option, ':', &method_trace_file_size_)) { - return false; - } - } else if (option == "-Xprofile:threadcpuclock") { - Trace::SetDefaultClockSource(kTraceClockSourceThreadCpu); - } else if (option == "-Xprofile:wallclock") { - Trace::SetDefaultClockSource(kTraceClockSourceWall); - } else if (option == "-Xprofile:dualclock") { - Trace::SetDefaultClockSource(kTraceClockSourceDual); - } else if (option == "-Xenable-profiler") { - profiler_options_.enabled_ = true; - } else if (StartsWith(option, "-Xprofile-filename:")) { - if (!ParseStringAfterChar(option, ':', &profile_output_filename_)) { - return false; - } - } else if (StartsWith(option, "-Xprofile-period:")) { - if (!ParseUnsignedInteger(option, ':', &profiler_options_.period_s_)) { - return false; - } - } else if (StartsWith(option, "-Xprofile-duration:")) { - if (!ParseUnsignedInteger(option, ':', &profiler_options_.duration_s_)) { - return false; - } - } else if (StartsWith(option, "-Xprofile-interval:")) { - if (!ParseUnsignedInteger(option, ':', &profiler_options_.interval_us_)) { - return false; - } - } else if (StartsWith(option, "-Xprofile-backoff:")) { - if (!ParseDouble(option, ':', 1.0, 10.0, &profiler_options_.backoff_coefficient_)) { - return false; - } - } else if (option == "-Xprofile-start-immediately") { - profiler_options_.start_immediately_ = true; - } else if (StartsWith(option, "-Xprofile-top-k-threshold:")) { - if (!ParseDouble(option, ':', 0.0, 100.0, &profiler_options_.top_k_threshold_)) { - return false; - } - } else if (StartsWith(option, "-Xprofile-top-k-change-threshold:")) { - if (!ParseDouble(option, ':', 0.0, 100.0, &profiler_options_.top_k_change_threshold_)) { - return false; - } - } else if (option == "-Xprofile-type:method") { - profiler_options_.profile_type_ = kProfilerMethod; - } else if (option == "-Xprofile-type:stack") { - profiler_options_.profile_type_ = kProfilerBoundedStack; - } else if (StartsWith(option, "-Xprofile-max-stack-depth:")) { - if (!ParseUnsignedInteger(option, ':', &profiler_options_.max_stack_depth_)) { - return false; + void(*hook_abort)() = reinterpret_cast(const_cast(hook)); + if (runtime_options != nullptr) { + runtime_options->Set(M::HookAbort, hook_abort); } - } else if (StartsWith(option, "-Xcompiler:")) { - if (!ParseStringAfterChar(option, ':', &compiler_executable_)) { - return false; - } - } else if (option == "-Xcompiler-option") { - i++; - if (i == options.size()) { - Usage("Missing required compiler option for %s\n", option.c_str()); - return false; + hook_abort_ = hook_abort; + } else { + // It is a regular option, that doesn't have a known 'second' value. + // Push it on to the regular options which will be parsed by our parser. + if (out_options != nullptr) { + out_options->push_back(option); } - compiler_options_.push_back(options[i].first); - } else if (option == "-Ximage-compiler-option") { - i++; - if (i == options.size()) { - Usage("Missing required compiler option for %s\n", option.c_str()); - return false; + } + } + + return true; +} + +bool ParsedOptions::Parse(const RuntimeOptions& options, bool ignore_unrecognized, + RuntimeArgumentMap* runtime_options) { +// gLogVerbosity.class_linker = true; // TODO: don't check this in! +// gLogVerbosity.compiler = true; // TODO: don't check this in! +// gLogVerbosity.gc = true; // TODO: don't check this in! +// gLogVerbosity.heap = true; // TODO: don't check this in! +// gLogVerbosity.jdwp = true; // TODO: don't check this in! +// gLogVerbosity.jni = true; // TODO: don't check this in! +// gLogVerbosity.monitor = true; // TODO: don't check this in! +// gLogVerbosity.profiler = true; // TODO: don't check this in! +// gLogVerbosity.signals = true; // TODO: don't check this in! +// gLogVerbosity.startup = true; // TODO: don't check this in! +// gLogVerbosity.third_party_jni = true; // TODO: don't check this in! +// gLogVerbosity.threads = true; // TODO: don't check this in! +// gLogVerbosity.verifier = true; // TODO: don't check this in! + + for (size_t i = 0; i < options.size(); ++i) { + if (true && options[0].first == "-Xzygote") { + LOG(INFO) << "option[" << i << "]=" << options[i].first; + } + } + + auto parser = MakeParser(ignore_unrecognized); + + // Convert to a simple string list (without the magic pointer options) + std::vector argv_list; + if (!ProcessSpecialOptions(options, nullptr, &argv_list)) { + return false; + } + + CmdlineResult parse_result = parser->Parse(argv_list); + + // Handle parse errors by displaying the usage and potentially exiting. + if (parse_result.IsError()) { + if (parse_result.GetStatus() == CmdlineResult::kUsage) { + UsageMessage(stdout, "%s\n", parse_result.GetMessage().c_str()); + Exit(0); + } else if (parse_result.GetStatus() == CmdlineResult::kUnknown && !ignore_unrecognized) { + Usage("%s\n", parse_result.GetMessage().c_str()); + return false; + } else { + Usage("%s\n", parse_result.GetMessage().c_str()); + Exit(0); + } + + UNREACHABLE(); + return false; + } + + using M = RuntimeArgumentMap; + RuntimeArgumentMap args = parser->ReleaseArgumentsMap(); + + // -help, -showversion, etc. + if (args.Exists(M::Help)) { + Usage(nullptr); + return false; + } else if (args.Exists(M::ShowVersion)) { + UsageMessage(stdout, "ART version %s\n", Runtime::GetVersion()); + Exit(0); + } else if (args.Exists(M::BootClassPath)) { + LOG(INFO) << "setting boot class path to " << *args.Get(M::BootClassPath); + } + + // Set a default boot class path if we didn't get an explicit one via command line. + if (getenv("BOOTCLASSPATH") != nullptr) { + args.SetIfMissing(M::BootClassPath, std::string(getenv("BOOTCLASSPATH"))); + } + + // Set a default class path if we didn't get an explicit one via command line. + if (getenv("CLASSPATH") != nullptr) { + args.SetIfMissing(M::ClassPath, std::string(getenv("CLASSPATH"))); + } + + // Default to number of processors minus one since the main GC thread also does work. + args.SetIfMissing(M::ParallelGCThreads, + static_cast(sysconf(_SC_NPROCESSORS_CONF) - 1u)); + + // -Xverbose: + { + LogVerbosity *log_verbosity = args.Get(M::Verbose); + if (log_verbosity != nullptr) { + gLogVerbosity = *log_verbosity; + } + } + + // -Xprofile: + Trace::SetDefaultClockSource(args.GetOrDefault(M::ProfileClock)); + + if (!ProcessSpecialOptions(options, &args, nullptr)) { + return false; + } + + { + // If not set, background collector type defaults to homogeneous compaction. + // If foreground is GSS, use GSS as background collector. + // If not low memory mode, semispace otherwise. + + gc::CollectorType background_collector_type_; + gc::CollectorType collector_type_ = (XGcOption{}).collector_type_; // NOLINT [whitespace/braces] [5] + bool low_memory_mode_ = args.Exists(M::LowMemoryMode); + + background_collector_type_ = args.GetOrDefault(M::BackgroundGc); + { + XGcOption* xgc = args.Get(M::GcOption); + if (xgc != nullptr && xgc->collector_type_ != gc::kCollectorTypeNone) { + collector_type_ = xgc->collector_type_; } - image_compiler_options_.push_back(options[i].first); - } else if (StartsWith(option, "-Xverify:")) { - std::string verify_mode = option.substr(strlen("-Xverify:")); - if (verify_mode == "none") { - verify_ = false; - } else if (verify_mode == "remote" || verify_mode == "all") { - verify_ = true; + } + + if (background_collector_type_ == gc::kCollectorTypeNone) { + if (collector_type_ != gc::kCollectorTypeGSS) { + background_collector_type_ = low_memory_mode_ ? + gc::kCollectorTypeSS : gc::kCollectorTypeHomogeneousSpaceCompact; } else { - Usage("Unknown -Xverify option %s\n", verify_mode.c_str()); - return false; - } - } else if (StartsWith(option, "-XX:NativeBridge=")) { - if (!ParseStringAfterChar(option, '=', &native_bridge_library_filename_)) { - return false; + background_collector_type_ = collector_type_; } - } else if (StartsWith(option, "-ea") || - StartsWith(option, "-da") || - StartsWith(option, "-enableassertions") || - StartsWith(option, "-disableassertions") || - (option == "--runtime-arg") || - (option == "-esa") || - (option == "-dsa") || - (option == "-enablesystemassertions") || - (option == "-disablesystemassertions") || - (option == "-Xrs") || - StartsWith(option, "-Xint:") || - StartsWith(option, "-Xdexopt:") || - (option == "-Xnoquithandler") || - StartsWith(option, "-Xjnigreflimit:") || - (option == "-Xgenregmap") || - (option == "-Xnogenregmap") || - StartsWith(option, "-Xverifyopt:") || - (option == "-Xcheckdexsum") || - (option == "-Xincludeselectedop") || - StartsWith(option, "-Xjitop:") || - (option == "-Xincludeselectedmethod") || - StartsWith(option, "-Xjitthreshold:") || - StartsWith(option, "-Xjitcodecachesize:") || - (option == "-Xjitblocking") || - StartsWith(option, "-Xjitmethod:") || - StartsWith(option, "-Xjitclass:") || - StartsWith(option, "-Xjitoffset:") || - StartsWith(option, "-Xjitconfig:") || - (option == "-Xjitcheckcg") || - (option == "-Xjitverbose") || - (option == "-Xjitprofile") || - (option == "-Xjitdisableopt") || - (option == "-Xjitsuspendpoll") || - StartsWith(option, "-XX:mainThreadStackSize=")) { - // Ignored for backwards compatibility. - } else if (!ignore_unrecognized) { - Usage("Unrecognized option %s\n", option.c_str()); - return false; } - } - // If not set, background collector type defaults to homogeneous compaction. - // If foreground is GSS, use GSS as background collector. - // If not low memory mode, semispace otherwise. - if (background_collector_type_ == gc::kCollectorTypeNone) { - if (collector_type_ != gc::kCollectorTypeGSS) { - background_collector_type_ = low_memory_mode_ ? - gc::kCollectorTypeSS : gc::kCollectorTypeHomogeneousSpaceCompact; - } else { - background_collector_type_ = collector_type_; + + args.Set(M::BackgroundGc, BackgroundGcOption { background_collector_type_ }); + { + XGcOption* xgc = args.Get(M::GcOption); + if (xgc != nullptr) { + xgc->collector_type_ = collector_type_; + args.Set(M::GcOption, *xgc); + } } } @@ -723,38 +485,45 @@ bool ParsedOptions::Parse(const RuntimeOptions& options, bool ignore_unrecognize std::string core_jar("/core-hostdex.jar"); std::string core_libart_jar("/core-libart-hostdex.jar"); #endif - size_t core_jar_pos = boot_class_path_string_.find(core_jar); + auto boot_class_path_string = args.GetOrDefault(M::BootClassPath); + + size_t core_jar_pos = boot_class_path_string.find(core_jar); if (core_jar_pos != std::string::npos) { - boot_class_path_string_.replace(core_jar_pos, core_jar.size(), core_libart_jar); + boot_class_path_string.replace(core_jar_pos, core_jar.size(), core_libart_jar); + args.Set(M::BootClassPath, boot_class_path_string); } - if (!boot_class_path_locations_string_.empty()) { - std::vector files; - Split(boot_class_path_string_, ':', &files); + { + auto&& boot_class_path = args.GetOrDefault(M::BootClassPath); + auto&& boot_class_path_locations = args.GetOrDefault(M::BootClassPathLocations); + if (args.Exists(M::BootClassPathLocations)) { + size_t boot_class_path_count = ParseStringList<':'>::Split(boot_class_path).Size(); - std::vector locations; - Split(boot_class_path_locations_string_, ':', &locations); - - if (files.size() != locations.size()) { - Usage("The number of boot class path files does not match" - " the number of boot class path locations given\n" - " boot class path files (%zu): %s\n" - " boot class path locations (%zu): %s\n", - files.size(), boot_class_path_string_.c_str(), - locations.size(), boot_class_path_locations_string_.c_str()); - return false; + if (boot_class_path_count != boot_class_path_locations.Size()) { + Usage("The number of boot class path files does not match" + " the number of boot class path locations given\n" + " boot class path files (%zu): %s\n" + " boot class path locations (%zu): %s\n", + boot_class_path.size(), boot_class_path_string.c_str(), + boot_class_path_locations.Size(), boot_class_path_locations.Join().c_str()); + return false; + } } } - if (compiler_callbacks_ == nullptr && image_.empty()) { - image_ += GetAndroidRoot(); - image_ += "/framework/boot.art"; + if (!args.Exists(M::CompilerCallbacksPtr) && !args.Exists(M::Image)) { + std::string image = GetAndroidRoot(); + image += "/framework/boot.art"; + args.Set(M::Image, image); } - if (heap_growth_limit_ == 0) { - heap_growth_limit_ = heap_maximum_size_; + + if (args.GetOrDefault(M::HeapGrowthLimit) == 0u) { // 0 means no growth limit + args.Set(M::HeapGrowthLimit, args.GetOrDefault(M::MemoryMaximumSize)); } + + *runtime_options = std::move(args); return true; -} // NOLINT(readability/fn_size) +} void ParsedOptions::Exit(int status) { hook_exit_(status); @@ -831,7 +600,7 @@ void ParsedOptions::Usage(const char* fmt, ...) { UsageMessage(stream, " -Xgc:[no]presweepingverify\n"); UsageMessage(stream, " -Ximage:filename\n"); UsageMessage(stream, " -Xbootclasspath-locations:bootclasspath\n" - " (override the dex locations of the -Xbootclasspath files)\n"); + " (override the dex locations of the -Xbootclasspath files)\n"); UsageMessage(stream, " -XX:+DisableExplicitGC\n"); UsageMessage(stream, " -XX:ParallelGCThreads=integervalue\n"); UsageMessage(stream, " -XX:ConcGCThreads=integervalue\n"); @@ -907,73 +676,4 @@ void ParsedOptions::Usage(const char* fmt, ...) { Exit((error) ? 1 : 0); } -bool ParsedOptions::ParseStringAfterChar(const std::string& s, char c, std::string* parsed_value) { - std::string::size_type colon = s.find(c); - if (colon == std::string::npos) { - Usage("Missing char %c in option %s\n", c, s.c_str()); - return false; - } - // Add one to remove the char we were trimming until. - *parsed_value = s.substr(colon + 1); - return true; -} - -bool ParsedOptions::ParseInteger(const std::string& s, char after_char, int* parsed_value) { - std::string::size_type colon = s.find(after_char); - if (colon == std::string::npos) { - Usage("Missing char %c in option %s\n", after_char, s.c_str()); - return false; - } - const char* begin = &s[colon + 1]; - char* end; - size_t result = strtoul(begin, &end, 10); - if (begin == end || *end != '\0') { - Usage("Failed to parse integer from %s\n", s.c_str()); - return false; - } - *parsed_value = result; - return true; -} - -bool ParsedOptions::ParseUnsignedInteger(const std::string& s, char after_char, - unsigned int* parsed_value) { - int i; - if (!ParseInteger(s, after_char, &i)) { - return false; - } - if (i < 0) { - Usage("Negative value %d passed for unsigned option %s\n", i, s.c_str()); - return false; - } - *parsed_value = i; - return true; -} - -bool ParsedOptions::ParseDouble(const std::string& option, char after_char, - double min, double max, double* parsed_value) { - std::string substring; - if (!ParseStringAfterChar(option, after_char, &substring)) { - return false; - } - bool sane_val = true; - double value; - if ((false)) { - // TODO: this doesn't seem to work on the emulator. b/15114595 - std::stringstream iss(substring); - iss >> value; - // Ensure that we have a value, there was no cruft after it and it satisfies a sensible range. - sane_val = iss.eof() && (value >= min) && (value <= max); - } else { - char* end = nullptr; - value = strtod(substring.c_str(), &end); - sane_val = *end == '\0' && value >= min && value <= max; - } - if (!sane_val) { - Usage("Invalid double value %s for option %s\n", substring.c_str(), option.c_str()); - return false; - } - *parsed_value = value; - return true; -} - } // namespace art diff --git a/runtime/parsed_options.h b/runtime/parsed_options.h index c7162b826..529dd5ce1 100644 --- a/runtime/parsed_options.h +++ b/runtime/parsed_options.h @@ -27,92 +27,44 @@ #include "gc/space/large_object_space.h" #include "arch/instruction_set.h" #include "profiler_options.h" +#include "runtime_options.h" namespace art { class CompilerCallbacks; class DexFile; +struct RuntimeArgumentMap; typedef std::vector> RuntimeOptions; +template class TVariantMapKey> +struct CmdlineParser; + class ParsedOptions { public: - // returns null if problem parsing and ignore_unrecognized is false - static ParsedOptions* Create(const RuntimeOptions& options, bool ignore_unrecognized); - - std::string boot_class_path_string_; - std::string boot_class_path_locations_string_; - std::string class_path_string_; - std::string image_; - bool check_jni_; - bool force_copy_; - std::string jni_trace_; - std::string native_bridge_library_filename_; - CompilerCallbacks* compiler_callbacks_; - bool is_zygote_; - bool must_relocate_; - bool dex2oat_enabled_; - bool image_dex2oat_enabled_; - std::string patchoat_executable_; - bool interpreter_only_; - bool is_explicit_gc_disabled_; - bool use_tlab_; - bool verify_pre_gc_heap_; - bool verify_pre_sweeping_heap_; - bool verify_post_gc_heap_; - bool verify_pre_gc_rosalloc_; - bool verify_pre_sweeping_rosalloc_; - bool verify_post_gc_rosalloc_; - unsigned int long_pause_log_threshold_; - unsigned int long_gc_log_threshold_; - bool dump_gc_performance_on_shutdown_; - bool ignore_max_footprint_; - size_t heap_initial_size_; - size_t heap_maximum_size_; - size_t heap_growth_limit_; - size_t heap_min_free_; - size_t heap_max_free_; - size_t heap_non_moving_space_capacity_; - gc::space::LargeObjectSpaceType large_object_space_type_; - size_t large_object_threshold_; - double heap_target_utilization_; - double foreground_heap_growth_multiplier_; - unsigned int parallel_gc_threads_; - unsigned int conc_gc_threads_; - gc::CollectorType collector_type_; - gc::CollectorType background_collector_type_; - size_t stack_size_; - unsigned int max_spins_before_thin_lock_inflation_; - bool low_memory_mode_; - unsigned int lock_profiling_threshold_; - std::string stack_trace_file_; - bool method_trace_; - std::string method_trace_file_; - unsigned int method_trace_file_size_; + using RuntimeParser = CmdlineParser; + // Create a parser that can turn user-defined input into a RuntimeArgumentMap. + // This visibility is effectively for testing-only, and normal code does not + // need to create its own parser. + static std::unique_ptr MakeParser(bool ignore_unrecognized); + + // returns true if parsing succeeds, and stores the resulting options into runtime_options + static ParsedOptions* Create(const RuntimeOptions& options, bool ignore_unrecognized, + RuntimeArgumentMap* runtime_options); + bool (*hook_is_sensitive_thread_)(); jint (*hook_vfprintf_)(FILE* stream, const char* format, va_list ap); void (*hook_exit_)(jint status); void (*hook_abort_)(); - std::vector properties_; - std::string compiler_executable_; - std::vector compiler_options_; - std::vector image_compiler_options_; - ProfilerOptions profiler_options_; - std::string profile_output_filename_; - TraceClockSource profile_clock_source_; - bool verify_; - InstructionSet image_isa_; - - // Whether or not we use homogeneous space compaction to avoid OOM errors. If enabled, - // the heap will attempt to create an extra space which enables compacting from a malloc space to - // another malloc space when we are about to throw OOM. - bool use_homogeneous_space_compaction_for_oom_; - // Minimal interval allowed between two homogeneous space compactions caused by OOM. - uint64_t min_interval_homogeneous_space_compaction_by_oom_; private: ParsedOptions(); + bool ProcessSpecialOptions(const RuntimeOptions& options, + RuntimeArgumentMap* runtime_options, + std::vector* out_options); + void Usage(const char* fmt, ...); void UsageMessage(FILE* stream, const char* fmt, ...); void UsageMessageV(FILE* stream, const char* fmt, va_list ap); @@ -120,13 +72,8 @@ class ParsedOptions { void Exit(int status); void Abort(); - bool Parse(const RuntimeOptions& options, bool ignore_unrecognized); - bool ParseXGcOption(const std::string& option); - bool ParseStringAfterChar(const std::string& option, char after_char, std::string* parsed_value); - bool ParseInteger(const std::string& option, char after_char, int* parsed_value); - bool ParseUnsignedInteger(const std::string& option, char after_char, unsigned int* parsed_value); - bool ParseDouble(const std::string& option, char after_char, double min, double max, - double* parsed_value); + bool Parse(const RuntimeOptions& options, bool ignore_unrecognized, + RuntimeArgumentMap* runtime_options); }; } // namespace art diff --git a/runtime/parsed_options_test.cc b/runtime/parsed_options_test.cc index 61481b16e..f68b6329c 100644 --- a/runtime/parsed_options_test.cc +++ b/runtime/parsed_options_test.cc @@ -22,7 +22,7 @@ namespace art { -class ParsedOptionsTest : public CommonRuntimeTest {}; +class ParsedOptionsTest : public ::testing::Test {}; TEST_F(ParsedOptionsTest, ParsedOptions) { void* test_vfprintf = reinterpret_cast(0xa); @@ -30,7 +30,7 @@ TEST_F(ParsedOptionsTest, ParsedOptions) { void* test_exit = reinterpret_cast(0xc); void* null = reinterpret_cast(NULL); - std::string lib_core(GetLibCoreDexFileName()); + std::string lib_core(CommonRuntimeTest::GetLibCoreDexFileName()); std::string boot_class_path; boot_class_path += "-Xbootclasspath:"; @@ -54,20 +54,28 @@ TEST_F(ParsedOptionsTest, ParsedOptions) { options.push_back(std::make_pair("vfprintf", test_vfprintf)); options.push_back(std::make_pair("abort", test_abort)); options.push_back(std::make_pair("exit", test_exit)); - std::unique_ptr parsed(ParsedOptions::Create(options, false)); + + RuntimeArgumentMap map; + std::unique_ptr parsed(ParsedOptions::Create(options, false, &map)); ASSERT_TRUE(parsed.get() != NULL); + ASSERT_NE(0u, map.Size()); + + using Opt = RuntimeArgumentMap; - EXPECT_EQ(lib_core, parsed->boot_class_path_string_); - EXPECT_EQ(lib_core, parsed->class_path_string_); - EXPECT_EQ(std::string("boot_image"), parsed->image_); - EXPECT_EQ(true, parsed->check_jni_); - EXPECT_EQ(2048U, parsed->heap_initial_size_); - EXPECT_EQ(4 * KB, parsed->heap_maximum_size_); - EXPECT_EQ(1 * MB, parsed->stack_size_); - EXPECT_DOUBLE_EQ(0.75, parsed->heap_target_utilization_); - EXPECT_TRUE(test_vfprintf == parsed->hook_vfprintf_); - EXPECT_TRUE(test_exit == parsed->hook_exit_); - EXPECT_TRUE(test_abort == parsed->hook_abort_); +#define EXPECT_PARSED_EQ(expected, actual_key) EXPECT_EQ(expected, map.GetOrDefault(actual_key)) +#define EXPECT_PARSED_EXISTS(actual_key) EXPECT_TRUE(map.Exists(actual_key)) + + EXPECT_PARSED_EQ(lib_core, Opt::BootClassPath); + EXPECT_PARSED_EQ(lib_core, Opt::ClassPath); + EXPECT_PARSED_EQ(std::string("boot_image"), Opt::Image); + EXPECT_PARSED_EXISTS(Opt::CheckJni); + EXPECT_PARSED_EQ(2048U, Opt::MemoryInitialSize); + EXPECT_PARSED_EQ(4 * KB, Opt::MemoryMaximumSize); + EXPECT_PARSED_EQ(1 * MB, Opt::StackSize); + EXPECT_DOUBLE_EQ(0.75, map.GetOrDefault(Opt::HeapTargetUtilization)); + EXPECT_TRUE(test_vfprintf == map.GetOrDefault(Opt::HookVfprintf)); + EXPECT_TRUE(test_exit == map.GetOrDefault(Opt::HookExit)); + EXPECT_TRUE(test_abort == map.GetOrDefault(Opt::HookAbort)); EXPECT_TRUE(VLOG_IS_ON(class_linker)); EXPECT_FALSE(VLOG_IS_ON(compiler)); EXPECT_FALSE(VLOG_IS_ON(heap)); @@ -78,9 +86,11 @@ TEST_F(ParsedOptionsTest, ParsedOptions) { EXPECT_FALSE(VLOG_IS_ON(startup)); EXPECT_FALSE(VLOG_IS_ON(third_party_jni)); EXPECT_FALSE(VLOG_IS_ON(threads)); - ASSERT_EQ(2U, parsed->properties_.size()); - EXPECT_EQ("foo=bar", parsed->properties_[0]); - EXPECT_EQ("baz=qux", parsed->properties_[1]); + + auto&& properties_list = map.GetOrDefault(Opt::PropertiesList); + ASSERT_EQ(2U, properties_list.size()); + EXPECT_EQ("foo=bar", properties_list[0]); + EXPECT_EQ("baz=qux", properties_list[1]); } } // namespace art diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 3acac3a1f..4bb17413d 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include @@ -106,6 +106,8 @@ #include "profiler.h" #include "quick/quick_method_frame_info.h" #include "reflection.h" +#include "runtime_options.h" +#include "ScopedLocalRef.h" #include "scoped_thread_state_change.h" #include "sigchain.h" #include "signal_catcher.h" @@ -711,8 +713,11 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) MemMap::Init(); - std::unique_ptr options(ParsedOptions::Create(raw_options, ignore_unrecognized)); - if (options.get() == nullptr) { + using Opt = RuntimeArgumentMap; + RuntimeArgumentMap runtime_options; + std::unique_ptr parsed_options( + ParsedOptions::Create(raw_options, ignore_unrecognized, &runtime_options)); + if (parsed_options.get() == nullptr) { LOG(ERROR) << "Failed to parse options"; return false; } @@ -720,76 +725,79 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) QuasiAtomic::Startup(); - Monitor::Init(options->lock_profiling_threshold_, options->hook_is_sensitive_thread_); + Monitor::Init(runtime_options.GetOrDefault(Opt::LockProfThreshold), + runtime_options.GetOrDefault(Opt::HookIsSensitiveThread)); - boot_class_path_string_ = options->boot_class_path_string_; - class_path_string_ = options->class_path_string_; - properties_ = options->properties_; + boot_class_path_string_ = runtime_options.ReleaseOrDefault(Opt::BootClassPath); + class_path_string_ = runtime_options.ReleaseOrDefault(Opt::ClassPath); + properties_ = runtime_options.ReleaseOrDefault(Opt::PropertiesList); - compiler_callbacks_ = options->compiler_callbacks_; - patchoat_executable_ = options->patchoat_executable_; - must_relocate_ = options->must_relocate_; - is_zygote_ = options->is_zygote_; - is_explicit_gc_disabled_ = options->is_explicit_gc_disabled_; - dex2oat_enabled_ = options->dex2oat_enabled_; - image_dex2oat_enabled_ = options->image_dex2oat_enabled_; + compiler_callbacks_ = runtime_options.GetOrDefault(Opt::CompilerCallbacksPtr); + patchoat_executable_ = runtime_options.ReleaseOrDefault(Opt::PatchOat); + must_relocate_ = runtime_options.GetOrDefault(Opt::Relocate); + is_zygote_ = runtime_options.Exists(Opt::Zygote); + is_explicit_gc_disabled_ = runtime_options.Exists(Opt::DisableExplicitGC); + dex2oat_enabled_ = runtime_options.GetOrDefault(Opt::Dex2Oat); + image_dex2oat_enabled_ = runtime_options.GetOrDefault(Opt::ImageDex2Oat); - vfprintf_ = options->hook_vfprintf_; - exit_ = options->hook_exit_; - abort_ = options->hook_abort_; + vfprintf_ = runtime_options.GetOrDefault(Opt::HookVfprintf); + exit_ = runtime_options.GetOrDefault(Opt::HookExit); + abort_ = runtime_options.GetOrDefault(Opt::HookAbort); - default_stack_size_ = options->stack_size_; - stack_trace_file_ = options->stack_trace_file_; + default_stack_size_ = runtime_options.GetOrDefault(Opt::StackSize); + stack_trace_file_ = runtime_options.ReleaseOrDefault(Opt::StackTraceFile); - compiler_executable_ = options->compiler_executable_; - compiler_options_ = options->compiler_options_; - image_compiler_options_ = options->image_compiler_options_; - image_location_ = options->image_; + compiler_executable_ = runtime_options.ReleaseOrDefault(Opt::Compiler); + compiler_options_ = runtime_options.ReleaseOrDefault(Opt::CompilerOptions); + image_compiler_options_ = runtime_options.ReleaseOrDefault(Opt::ImageCompilerOptions); + image_location_ = runtime_options.GetOrDefault(Opt::Image); - max_spins_before_thin_lock_inflation_ = options->max_spins_before_thin_lock_inflation_; + max_spins_before_thin_lock_inflation_ = + runtime_options.GetOrDefault(Opt::MaxSpinsBeforeThinLockInflation); monitor_list_ = new MonitorList; monitor_pool_ = MonitorPool::Create(); thread_list_ = new ThreadList; intern_table_ = new InternTable; - verify_ = options->verify_; + verify_ = runtime_options.GetOrDefault(Opt::Verify); - if (options->interpreter_only_) { + if (runtime_options.Exists(Opt::Interpret)) { GetInstrumentation()->ForceInterpretOnly(); } - heap_ = new gc::Heap(options->heap_initial_size_, - options->heap_growth_limit_, - options->heap_min_free_, - options->heap_max_free_, - options->heap_target_utilization_, - options->foreground_heap_growth_multiplier_, - options->heap_maximum_size_, - options->heap_non_moving_space_capacity_, - options->image_, - options->image_isa_, - options->collector_type_, - options->background_collector_type_, - options->large_object_space_type_, - options->large_object_threshold_, - options->parallel_gc_threads_, - options->conc_gc_threads_, - options->low_memory_mode_, - options->long_pause_log_threshold_, - options->long_gc_log_threshold_, - options->ignore_max_footprint_, - options->use_tlab_, - options->verify_pre_gc_heap_, - options->verify_pre_sweeping_heap_, - options->verify_post_gc_heap_, - options->verify_pre_gc_rosalloc_, - options->verify_pre_sweeping_rosalloc_, - options->verify_post_gc_rosalloc_, - options->use_homogeneous_space_compaction_for_oom_, - options->min_interval_homogeneous_space_compaction_by_oom_); - - dump_gc_performance_on_shutdown_ = options->dump_gc_performance_on_shutdown_; + XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption); + heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize), + runtime_options.GetOrDefault(Opt::HeapGrowthLimit), + runtime_options.GetOrDefault(Opt::HeapMinFree), + runtime_options.GetOrDefault(Opt::HeapMaxFree), + runtime_options.GetOrDefault(Opt::HeapTargetUtilization), + runtime_options.GetOrDefault(Opt::ForegroundHeapGrowthMultiplier), + runtime_options.GetOrDefault(Opt::MemoryMaximumSize), + runtime_options.GetOrDefault(Opt::NonMovingSpaceCapacity), + runtime_options.GetOrDefault(Opt::Image), + runtime_options.GetOrDefault(Opt::ImageInstructionSet), + xgc_option.collector_type_, + runtime_options.GetOrDefault(Opt::BackgroundGc), + runtime_options.GetOrDefault(Opt::LargeObjectSpace), + runtime_options.GetOrDefault(Opt::LargeObjectThreshold), + runtime_options.GetOrDefault(Opt::ParallelGCThreads), + runtime_options.GetOrDefault(Opt::ConcGCThreads), + runtime_options.Exists(Opt::LowMemoryMode), + runtime_options.GetOrDefault(Opt::LongPauseLogThreshold), + runtime_options.GetOrDefault(Opt::LongGCLogThreshold), + runtime_options.Exists(Opt::IgnoreMaxFootprint), + runtime_options.Exists(Opt::UseTLAB), + xgc_option.verify_pre_gc_heap_, + xgc_option.verify_pre_sweeping_heap_, + xgc_option.verify_post_gc_heap_, + xgc_option.verify_pre_gc_rosalloc_, + xgc_option.verify_pre_sweeping_rosalloc_, + xgc_option.verify_post_gc_rosalloc_, + runtime_options.GetOrDefault(Opt::EnableHSpaceCompactForOOM), + runtime_options.GetOrDefault(Opt::HSpaceCompactForOOMMinIntervalsMs)); + + dump_gc_performance_on_shutdown_ = runtime_options.Exists(Opt::DumpGCPerformanceOnShutdown); BlockSignals(); InitPlatformSignalHandlers(); @@ -840,7 +848,7 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) } } - java_vm_ = new JavaVMExt(this, options.get()); + java_vm_ = new JavaVMExt(this, runtime_options); Thread::Startup(); @@ -879,16 +887,20 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) Split(boot_class_path_string_, ':', &dex_filenames); std::vector dex_locations; - if (options->boot_class_path_locations_string_.empty()) { + if (!runtime_options.Exists(Opt::BootClassPathLocations)) { dex_locations = dex_filenames; } else { - Split(options->boot_class_path_locations_string_, ':', &dex_locations); + dex_locations = runtime_options.GetOrDefault(Opt::BootClassPathLocations); CHECK_EQ(dex_filenames.size(), dex_locations.size()); } std::vector> boot_class_path; - OpenDexFiles(dex_filenames, dex_locations, options->image_, &boot_class_path); + OpenDexFiles(dex_filenames, + dex_locations, + runtime_options.GetOrDefault(Opt::Image), + &boot_class_path); class_linker_->InitWithoutImage(std::move(boot_class_path)); + // TODO: Should we move the following to InitWithoutImage? SetInstructionSet(kRuntimeISA); for (int i = 0; i < Runtime::kLastCalleeSaveType; i++) { @@ -907,20 +919,42 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) verifier::MethodVerifier::Init(); - method_trace_ = options->method_trace_; - method_trace_file_ = options->method_trace_file_; - method_trace_file_size_ = options->method_trace_file_size_; + method_trace_ = runtime_options.Exists(Opt::MethodTrace); + method_trace_file_ = runtime_options.ReleaseOrDefault(Opt::MethodTraceFile); + method_trace_file_size_ = runtime_options.ReleaseOrDefault(Opt::MethodTraceFileSize); - profile_output_filename_ = options->profile_output_filename_; - profiler_options_ = options->profiler_options_; + { + auto&& profiler_options = runtime_options.ReleaseOrDefault(Opt::ProfilerOpts); + profile_output_filename_ = profiler_options.output_file_name_; + + // TODO: Don't do this, just change ProfilerOptions to include the output file name? + ProfilerOptions other_options( + profiler_options.enabled_, + profiler_options.period_s_, + profiler_options.duration_s_, + profiler_options.interval_us_, + profiler_options.backoff_coefficient_, + profiler_options.start_immediately_, + profiler_options.top_k_threshold_, + profiler_options.top_k_change_threshold_, + profiler_options.profile_type_, + profiler_options.max_stack_depth_); + + profiler_options_ = other_options; + } // TODO: move this to just be an Trace::Start argument - Trace::SetDefaultClockSource(options->profile_clock_source_); + Trace::SetDefaultClockSource(runtime_options.GetOrDefault(Opt::ProfileClock)); - if (options->method_trace_) { + if (method_trace_) { ScopedThreadStateChange tsc(self, kWaitingForMethodTracingStart); - Trace::Start(options->method_trace_file_.c_str(), -1, options->method_trace_file_size_, 0, - false, false, 0); + Trace::Start(method_trace_file_.c_str(), + -1, + static_cast(method_trace_file_size_), + 0, + false, + false, + 0); } // Pre-allocate an OutOfMemoryError for the double-OOME case. @@ -964,7 +998,10 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) // Runtime::Start(): // DidForkFromZygote(kInitialize) -> try to initialize any native bridge given. // No-op wrt native bridge. - is_native_bridge_loaded_ = LoadNativeBridge(options->native_bridge_library_filename_); + { + std::string native_bridge_file_name = runtime_options.ReleaseOrDefault(Opt::NativeBridge); + is_native_bridge_loaded_ = LoadNativeBridge(native_bridge_file_name); + } VLOG(startup) << "Runtime::Init exiting"; return true; diff --git a/runtime/runtime_options.cc b/runtime/runtime_options.cc new file mode 100644 index 000000000..c54461e60 --- /dev/null +++ b/runtime/runtime_options.cc @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "runtime_options.h" + +#include "gc/heap.h" +#include "monitor.h" +#include "runtime.h" +#include "trace.h" +#include "utils.h" +#include "debugger.h" + +namespace art { + +// Specify storage for the RuntimeOptions keys. + +#define RUNTIME_OPTIONS_KEY(Type, Name, ...) const RuntimeArgumentMap::Key RuntimeArgumentMap::Name {__VA_ARGS__}; // NOLINT [readability/braces] [4] +#include "runtime_options.def" + +} // namespace art diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def new file mode 100644 index 000000000..022a2a5e9 --- /dev/null +++ b/runtime/runtime_options.def @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RUNTIME_OPTIONS_KEY +#error "Please #define RUNTIME_OPTIONS_KEY before #including this file" +#define RUNTIME_OPTIONS_KEY(...) // Don't display errors in this file in IDEs. +#endif + +// This file defines the list of keys for RuntimeOptions. +// These can be used with RuntimeOptions.Get/Set/etc, for example: +// RuntimeOptions opt; bool* dex2oat_enabled = opt.Get(RuntimeOptions::Dex2Oat); +// +// Column Descriptions: +// <> <> <> +// +// Default values are only used by Map::GetOrDefault(K). +// If a default value is omitted here, T{} is used as the default value, which is +// almost-always the value of the type as if it was memset to all 0. +// + +// Parse-able keys from the command line. +RUNTIME_OPTIONS_KEY (Unit, Zygote) +RUNTIME_OPTIONS_KEY (Unit, Help) +RUNTIME_OPTIONS_KEY (Unit, ShowVersion) +RUNTIME_OPTIONS_KEY (std::string, BootClassPath) +RUNTIME_OPTIONS_KEY (ParseStringList<':'>,BootClassPathLocations) // std::vector +RUNTIME_OPTIONS_KEY (std::string, ClassPath) +RUNTIME_OPTIONS_KEY (std::string, Image) +RUNTIME_OPTIONS_KEY (Unit, CheckJni) +RUNTIME_OPTIONS_KEY (Unit, JniOptsForceCopy) +RUNTIME_OPTIONS_KEY (JDWP::JdwpOptions, JdwpOptions) +RUNTIME_OPTIONS_KEY (MemoryKiB, MemoryMaximumSize, gc::Heap::kDefaultMaximumSize) // -Xmx +RUNTIME_OPTIONS_KEY (MemoryKiB, MemoryInitialSize, gc::Heap::kDefaultInitialSize) // -Xms +RUNTIME_OPTIONS_KEY (MemoryKiB, HeapGrowthLimit) // Default is 0 for unlimited +RUNTIME_OPTIONS_KEY (MemoryKiB, HeapMinFree, gc::Heap::kDefaultMinFree) +RUNTIME_OPTIONS_KEY (MemoryKiB, HeapMaxFree, gc::Heap::kDefaultMaxFree) +RUNTIME_OPTIONS_KEY (MemoryKiB, NonMovingSpaceCapacity, gc::Heap::kDefaultNonMovingSpaceCapacity) +RUNTIME_OPTIONS_KEY (double, HeapTargetUtilization, gc::Heap::kDefaultTargetUtilization) +RUNTIME_OPTIONS_KEY (double, ForegroundHeapGrowthMultiplier, gc::Heap::kDefaultHeapGrowthMultiplier) +RUNTIME_OPTIONS_KEY (unsigned int, ParallelGCThreads, 1u) +RUNTIME_OPTIONS_KEY (unsigned int, ConcGCThreads) +RUNTIME_OPTIONS_KEY (Memory<1>, StackSize) // -Xss +RUNTIME_OPTIONS_KEY (unsigned int, MaxSpinsBeforeThinLockInflation,Monitor::kDefaultMaxSpinsBeforeThinLockInflation) +RUNTIME_OPTIONS_KEY (MillisecondsToNanoseconds, \ + LongPauseLogThreshold, gc::Heap::kDefaultLongPauseLogThreshold) +RUNTIME_OPTIONS_KEY (MillisecondsToNanoseconds, \ + LongGCLogThreshold, gc::Heap::kDefaultLongGCLogThreshold) +RUNTIME_OPTIONS_KEY (Unit, DumpGCPerformanceOnShutdown) +RUNTIME_OPTIONS_KEY (Unit, IgnoreMaxFootprint) +RUNTIME_OPTIONS_KEY (Unit, LowMemoryMode) +RUNTIME_OPTIONS_KEY (Unit, UseTLAB) +RUNTIME_OPTIONS_KEY (bool, EnableHSpaceCompactForOOM, true) +RUNTIME_OPTIONS_KEY (MillisecondsToNanoseconds, \ + HSpaceCompactForOOMMinIntervalsMs,\ + MsToNs(100 * 1000)) // 100s +RUNTIME_OPTIONS_KEY (std::vector, \ + PropertiesList) // -D -D ... +RUNTIME_OPTIONS_KEY (std::string, JniTrace) +RUNTIME_OPTIONS_KEY (std::string, PatchOat) +RUNTIME_OPTIONS_KEY (bool, Relocate, kDefaultMustRelocate) +RUNTIME_OPTIONS_KEY (bool, Dex2Oat, true) +RUNTIME_OPTIONS_KEY (bool, ImageDex2Oat, true) + // kPoisonHeapReferences currently works with + // the interpreter only. + // TODO: make it work with the compiler. +RUNTIME_OPTIONS_KEY (bool, Interpret, (kPoisonHeapReferences || kUseReadBarrier)) // -Xint + // Disable the compiler for CC (for now). +RUNTIME_OPTIONS_KEY (XGcOption, GcOption) // -Xgc: +RUNTIME_OPTIONS_KEY (gc::space::LargeObjectSpaceType, \ + LargeObjectSpace, gc::Heap::kDefaultLargeObjectSpaceType) +RUNTIME_OPTIONS_KEY (Memory<1>, LargeObjectThreshold, gc::Heap::kDefaultLargeObjectThreshold) +RUNTIME_OPTIONS_KEY (BackgroundGcOption, BackgroundGc) + +RUNTIME_OPTIONS_KEY (Unit, DisableExplicitGC) +RUNTIME_OPTIONS_KEY (LogVerbosity, Verbose) +RUNTIME_OPTIONS_KEY (unsigned int, LockProfThreshold) +RUNTIME_OPTIONS_KEY (std::string, StackTraceFile) +RUNTIME_OPTIONS_KEY (Unit, MethodTrace) +RUNTIME_OPTIONS_KEY (std::string, MethodTraceFile, "/data/method-trace-file.bin") +RUNTIME_OPTIONS_KEY (unsigned int, MethodTraceFileSize, 10 * MB) +RUNTIME_OPTIONS_KEY (TraceClockSource, ProfileClock, kDefaultTraceClockSource) // -Xprofile: +RUNTIME_OPTIONS_KEY (TestProfilerOptions, ProfilerOpts) // -Xenable-profiler, -Xprofile-* +RUNTIME_OPTIONS_KEY (std::string, Compiler) +RUNTIME_OPTIONS_KEY (std::vector, \ + CompilerOptions) // -Xcompiler-option ... +RUNTIME_OPTIONS_KEY (std::vector, \ + ImageCompilerOptions) // -Ximage-compiler-option ... +RUNTIME_OPTIONS_KEY (bool, Verify, true) +RUNTIME_OPTIONS_KEY (std::string, NativeBridge) + +// Not parse-able from command line, but can be provided explicitly. +RUNTIME_OPTIONS_KEY (const std::vector*, \ + BootClassPathDexList) // TODO: make unique_ptr +RUNTIME_OPTIONS_KEY (InstructionSet, ImageInstructionSet, kRuntimeISA) +RUNTIME_OPTIONS_KEY (CompilerCallbacks*, CompilerCallbacksPtr) // TDOO: make unique_ptr +RUNTIME_OPTIONS_KEY (bool (*)(), HookIsSensitiveThread) +RUNTIME_OPTIONS_KEY (int32_t (*)(FILE* stream, const char* format, va_list ap), \ + HookVfprintf, vfprintf) +RUNTIME_OPTIONS_KEY (void (*)(int32_t status), \ + HookExit, exit) + // We don't call abort(3) by default; see + // Runtime::Abort. +RUNTIME_OPTIONS_KEY (void (*)(), HookAbort, nullptr) + + +#undef RUNTIME_OPTIONS_KEY diff --git a/runtime/runtime_options.h b/runtime/runtime_options.h new file mode 100644 index 000000000..ebd52d77d --- /dev/null +++ b/runtime/runtime_options.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_RUNTIME_OPTIONS_H_ +#define ART_RUNTIME_RUNTIME_OPTIONS_H_ + +#include "runtime/base/variant_map.h" +#include "cmdline/cmdline_types.h" // TODO: don't need to include this file here + +// Map keys +#include +#include +#include "runtime/base/logging.h" +#include "cmdline/unit.h" +#include "jdwp/jdwp.h" +#include "gc/collector_type.h" +#include "gc/space/large_object_space.h" +#include "profiler_options.h" +#include "arch/instruction_set.h" +#include +#include + +namespace art { + +class CompilerCallbacks; +class DexFile; +struct XGcOption; +struct BackgroundGcOption; +struct TestProfilerOptions; + +#define DECLARE_KEY(Type, Name) static const Key Name + + // Define a key that is usable with a RuntimeArgumentMap. + // This key will *not* work with other subtypes of VariantMap. + template + struct RuntimeArgumentMapKey : VariantMapKey { + RuntimeArgumentMapKey() {} + explicit RuntimeArgumentMapKey(TValue default_value) + : VariantMapKey(std::move(default_value)) {} + // Don't ODR-use constexpr default values, which means that Struct::Fields + // that are declared 'static constexpr T Name = Value' don't need to have a matching definition. + }; + + // Defines a type-safe heterogeneous key->value map. + // Use the VariantMap interface to look up or to store a RuntimeArgumentMapKey,Value pair. + // + // Example: + // auto map = RuntimeArgumentMap(); + // map.Set(RuntimeArgumentMap::HeapTargetUtilization, 5.0); + // double *target_utilization = map.Get(RuntimeArgumentMap); + // + struct RuntimeArgumentMap : VariantMap { + // This 'using' line is necessary to inherit the variadic constructor. + using VariantMap::VariantMap; + + // Make the next many usages of Key slightly shorter to type. + template + using Key = RuntimeArgumentMapKey; + + // List of key declarations, shorthand for 'static const Key Name' +#define RUNTIME_OPTIONS_KEY(Type, Name, ...) static const Key Name; +#include "runtime_options.def" + }; + +#undef DECLARE_KEY + + // using RuntimeOptions = RuntimeArgumentMap; +} // namespace art + +#endif // ART_RUNTIME_RUNTIME_OPTIONS_H_ diff --git a/runtime/trace.cc b/runtime/trace.cc index 5066e035b..0950abeb9 100644 --- a/runtime/trace.cc +++ b/runtime/trace.cc @@ -153,30 +153,30 @@ void Trace::SetDefaultClockSource(TraceClockSource clock_source) { #if defined(__linux__) default_clock_source_ = clock_source; #else - if (clock_source != kTraceClockSourceWall) { + if (clock_source != TraceClockSource::kWall) { LOG(WARNING) << "Ignoring tracing request to use CPU time."; } #endif } static uint16_t GetTraceVersion(TraceClockSource clock_source) { - return (clock_source == kTraceClockSourceDual) ? kTraceVersionDualClock + return (clock_source == TraceClockSource::kDual) ? kTraceVersionDualClock : kTraceVersionSingleClock; } static uint16_t GetRecordSize(TraceClockSource clock_source) { - return (clock_source == kTraceClockSourceDual) ? kTraceRecordSizeDualClock + return (clock_source == TraceClockSource::kDual) ? kTraceRecordSizeDualClock : kTraceRecordSizeSingleClock; } bool Trace::UseThreadCpuClock() { - return (clock_source_ == kTraceClockSourceThreadCpu) || - (clock_source_ == kTraceClockSourceDual); + return (clock_source_ == TraceClockSource::kThreadCpu) || + (clock_source_ == TraceClockSource::kDual); } bool Trace::UseWallClock() { - return (clock_source_ == kTraceClockSourceWall) || - (clock_source_ == kTraceClockSourceDual); + return (clock_source_ == TraceClockSource::kWall) || + (clock_source_ == TraceClockSource::kDual); } void Trace::MeasureClockOverhead() { diff --git a/runtime/utils.h b/runtime/utils.h index b5413e75b..1c2576c2d 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -549,6 +549,13 @@ struct FreeDelete { template using UniqueCPtr = std::unique_ptr; +// C++14 from-the-future import (std::make_unique) +// Invoke the constructor of 'T' with the provided args, and wrap the result in a unique ptr. +template +std::unique_ptr MakeUnique(Args&& ... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + } // namespace art #endif // ART_RUNTIME_UTILS_H_ -- 2.11.0