1 /* 2 * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 package org.graalvm.compiler.options; 26 27 import static jdk.vm.ci.services.Services.IS_BUILDING_NATIVE_IMAGE; 28 import static jdk.vm.ci.services.Services.IS_IN_NATIVE_IMAGE; 29 30 import java.util.ArrayList; 31 import java.util.Collection; 32 import java.util.Formatter; 33 import java.util.List; 34 import java.util.ServiceLoader; 35 36 import jdk.internal.vm.compiler.collections.EconomicMap; 37 import jdk.internal.vm.compiler.collections.MapCursor; 38 39 /** 40 * This class contains methods for parsing Graal options and matching them against a set of 41 * {@link OptionDescriptors}. The {@link OptionDescriptors} are loaded via a {@link ServiceLoader}. 42 */ 43 public class OptionsParser { 44 45 private static volatile List<OptionDescriptors> cachedOptionDescriptors; 46 47 /** 48 * Gets an iterable of available {@link OptionDescriptors}. 49 */ 50 public static Iterable<OptionDescriptors> getOptionsLoader() { 51 if (IS_IN_NATIVE_IMAGE || cachedOptionDescriptors != null) { 52 return cachedOptionDescriptors; 53 } 54 return ModuleSupport.getOptionsLoader(); 55 } 56 57 public static void setCachedOptionDescriptors(List<OptionDescriptors> list) { 58 assert IS_BUILDING_NATIVE_IMAGE : "Used to pre-initialize the option descriptors during native image generation"; 59 OptionsParser.cachedOptionDescriptors = list; 60 } 61 62 /** 63 * Parses a map representing assignments of values to options. 64 * 65 * @param optionSettings option settings (i.e., assignments of values to options) 66 * @param values the object in which to store the parsed values 67 * @param loader source of the available {@link OptionDescriptors} 68 * @throws IllegalArgumentException if there's a problem parsing {@code option} 69 */ 70 public static void parseOptions(EconomicMap<String, String> optionSettings, EconomicMap<OptionKey<?>, Object> values, Iterable<OptionDescriptors> loader) { 71 if (optionSettings != null && !optionSettings.isEmpty()) { 72 MapCursor<String, String> cursor = optionSettings.getEntries(); 73 while (cursor.advance()) { 74 parseOption(cursor.getKey(), cursor.getValue(), values, loader); 75 } 76 } 77 } 78 79 /** 80 * Parses a given option setting string and adds the parsed key and value {@code dst}. 81 * 82 * @param optionSetting a string matching the pattern {@code <name>=<value>} 83 */ 84 public static void parseOptionSettingTo(String optionSetting, EconomicMap<String, String> dst) { 85 int eqIndex = optionSetting.indexOf('='); 86 if (eqIndex == -1) { 87 throw new InternalError("Option setting has does not match the pattern <name>=<value>: " + optionSetting); 88 } 89 dst.put(optionSetting.substring(0, eqIndex), optionSetting.substring(eqIndex + 1)); 90 } 91 92 /** 93 * Looks up an {@link OptionDescriptor} based on a given name. 94 * 95 * @param loader source of the available {@link OptionDescriptors} 96 * @param name the name of the option to look up 97 * @return the {@link OptionDescriptor} whose name equals {@code name} or null if not such 98 * descriptor is available 99 */ 100 private static OptionDescriptor lookup(Iterable<OptionDescriptors> loader, String name) { 101 for (OptionDescriptors optionDescriptors : loader) { 102 OptionDescriptor desc = optionDescriptors.get(name); 103 if (desc != null) { 104 return desc; 105 } 106 } 107 return null; 108 } 109 110 /** 111 * Parses a given option name and value. 112 * 113 * @param name the option name 114 * @param uncheckedValue the unchecked value for the option 115 * @param values the object in which to store the parsed option and value 116 * @param loader source of the available {@link OptionDescriptors} 117 * @throws IllegalArgumentException if there's a problem parsing {@code option} 118 */ 119 public static void parseOption(String name, Object uncheckedValue, EconomicMap<OptionKey<?>, Object> values, Iterable<OptionDescriptors> loader) { 120 121 OptionDescriptor desc = lookup(loader, name); 122 if (desc == null) { 123 List<OptionDescriptor> matches = fuzzyMatch(loader, name); 124 Formatter msg = new Formatter(); 125 msg.format("Could not find option %s", name); 126 if (!matches.isEmpty()) { 127 msg.format("%nDid you mean one of the following?"); 128 for (OptionDescriptor match : matches) { 129 msg.format("%n %s=<value>", match.getName()); 130 } 131 } 132 throw new IllegalArgumentException(msg.toString()); 133 } 134 135 Class<?> optionType = desc.getOptionValueType(); 136 Object value; 137 if (!(uncheckedValue instanceof String)) { 138 if (optionType != uncheckedValue.getClass()) { 139 String type = optionType.getSimpleName(); 140 throw new IllegalArgumentException(type + " option '" + name + "' must have " + type + " value, not " + uncheckedValue.getClass() + " [toString: " + uncheckedValue + "]"); 141 } 142 value = uncheckedValue; 143 } else { 144 String valueString = (String) uncheckedValue; 145 if (optionType == Boolean.class) { 146 if ("true".equals(valueString)) { 147 value = Boolean.TRUE; 148 } else if ("false".equals(valueString)) { 149 value = Boolean.FALSE; 150 } else { 151 throw new IllegalArgumentException("Boolean option '" + name + "' must have value \"true\" or \"false\", not \"" + uncheckedValue + "\""); 152 } 153 } else if (optionType == String.class) { 154 value = valueString; 155 } else if (Enum.class.isAssignableFrom(optionType)) { 156 value = ((EnumOptionKey<?>) desc.getOptionKey()).valueOf(valueString); 157 } else { 158 if (valueString.isEmpty()) { 159 throw new IllegalArgumentException("Non empty value required for option '" + name + "'"); 160 } 161 try { 162 if (optionType == Float.class) { 163 value = Float.parseFloat(valueString); 164 } else if (optionType == Double.class) { 165 value = Double.parseDouble(valueString); 166 } else if (optionType == Integer.class) { 167 value = Integer.valueOf((int) parseLong(valueString)); 168 } else if (optionType == Long.class) { 169 value = Long.valueOf(parseLong(valueString)); 170 } else { 171 throw new IllegalArgumentException("Wrong value for option '" + name + "'"); 172 } 173 } catch (NumberFormatException nfe) { 174 throw new IllegalArgumentException("Value for option '" + name + "' has invalid number format: " + valueString); 175 } 176 } 177 } 178 179 desc.getOptionKey().update(values, value); 180 } 181 182 private static long parseLong(String v) { 183 String valueString = v.toLowerCase(); 184 long scale = 1; 185 if (valueString.endsWith("k")) { 186 scale = 1024L; 187 } else if (valueString.endsWith("m")) { 188 scale = 1024L * 1024L; 189 } else if (valueString.endsWith("g")) { 190 scale = 1024L * 1024L * 1024L; 191 } else if (valueString.endsWith("t")) { 192 scale = 1024L * 1024L * 1024L * 1024L; 193 } 194 195 if (scale != 1) { 196 /* Remove trailing scale character. */ 197 valueString = valueString.substring(0, valueString.length() - 1); 198 } 199 200 return Long.parseLong(valueString) * scale; 201 } 202 203 /** 204 * Compute string similarity based on Dice's coefficient. 205 * 206 * Ported from str_similar() in globals.cpp. 207 */ 208 static float stringSimiliarity(String str1, String str2) { 209 int hit = 0; 210 for (int i = 0; i < str1.length() - 1; ++i) { 211 for (int j = 0; j < str2.length() - 1; ++j) { 212 if ((str1.charAt(i) == str2.charAt(j)) && (str1.charAt(i + 1) == str2.charAt(j + 1))) { 213 ++hit; 214 break; 215 } 216 } 217 } 218 return 2.0f * hit / (str1.length() + str2.length()); 219 } 220 221 private static final float FUZZY_MATCH_THRESHOLD = 0.7F; 222 223 /** 224 * Returns the set of options that fuzzy match a given option name. 225 */ 226 private static List<OptionDescriptor> fuzzyMatch(Iterable<OptionDescriptors> loader, String optionName) { 227 List<OptionDescriptor> matches = new ArrayList<>(); 228 for (OptionDescriptors options : loader) { 229 collectFuzzyMatches(options, optionName, matches); 230 } 231 return matches; 232 } 233 234 /** 235 * Collects the set of options that fuzzy match a given option name. String similarity for fuzzy 236 * matching is based on Dice's coefficient. 237 * 238 * @param toSearch the set of option descriptors to search 239 * @param name the option name to search for 240 * @param matches the collection to which fuzzy matches of {@code name} will be added 241 * @return whether any fuzzy matches were found 242 */ 243 public static boolean collectFuzzyMatches(Iterable<OptionDescriptor> toSearch, String name, Collection<OptionDescriptor> matches) { 244 boolean found = false; 245 for (OptionDescriptor option : toSearch) { 246 float score = stringSimiliarity(option.getName(), name); 247 if (score >= FUZZY_MATCH_THRESHOLD) { 248 found = true; 249 matches.add(option); 250 } 251 } 252 return found; 253 } 254 }