1 /* 2 * Copyright (c) 2014, 2018, 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 boolean java8OrEarlier = System.getProperty("java.specification.version").compareTo("1.9") < 0; 55 ClassLoader loader; 56 if (java8OrEarlier) { 57 // On JDK 8, Graal and its extensions are loaded by same class loader. 58 loader = OptionDescriptors.class.getClassLoader(); 59 } else { 60 /* 61 * The Graal module (i.e., jdk.internal.vm.compiler) is loaded by the platform class 62 * loader as of JDK 9. Modules that depend on and extend Graal are loaded by the app 63 * class loader. As such, we need to start the provider search at the app class loader 64 * instead of the platform class loader. 65 */ 66 loader = ClassLoader.getSystemClassLoader(); 67 } 68 Iterable<OptionDescriptors> result = ServiceLoader.load(OptionDescriptors.class, loader); 69 if (IS_BUILDING_NATIVE_IMAGE) { 70 ArrayList<OptionDescriptors> optionDescriptors = new ArrayList<>(); 71 for (OptionDescriptors descriptors : result) { 72 optionDescriptors.add(descriptors); 73 } 74 OptionsParser.cachedOptionDescriptors = optionDescriptors; 75 } 76 return result; 77 } 78 79 /** 80 * Parses a map representing assignments of values to options. 81 * 82 * @param optionSettings option settings (i.e., assignments of values to options) 83 * @param values the object in which to store the parsed values 84 * @param loader source of the available {@link OptionDescriptors} 85 * @throws IllegalArgumentException if there's a problem parsing {@code option} 86 */ 87 public static void parseOptions(EconomicMap<String, String> optionSettings, EconomicMap<OptionKey<?>, Object> values, Iterable<OptionDescriptors> loader) { 88 if (optionSettings != null && !optionSettings.isEmpty()) { 89 MapCursor<String, String> cursor = optionSettings.getEntries(); 90 while (cursor.advance()) { 91 parseOption(cursor.getKey(), cursor.getValue(), values, loader); 92 } 93 } 94 } 95 96 /** 97 * Parses a given option setting string and adds the parsed key and value {@code dst}. 98 * 99 * @param optionSetting a string matching the pattern {@code <name>=<value>} 100 */ 101 public static void parseOptionSettingTo(String optionSetting, EconomicMap<String, String> dst) { 102 int eqIndex = optionSetting.indexOf('='); 103 if (eqIndex == -1) { 104 throw new InternalError("Option setting has does not match the pattern <name>=<value>: " + optionSetting); 105 } 106 dst.put(optionSetting.substring(0, eqIndex), optionSetting.substring(eqIndex + 1)); 107 } 108 109 /** 110 * Looks up an {@link OptionDescriptor} based on a given name. 111 * 112 * @param loader source of the available {@link OptionDescriptors} 113 * @param name the name of the option to look up 114 * @return the {@link OptionDescriptor} whose name equals {@code name} or null if not such 115 * descriptor is available 116 */ 117 private static OptionDescriptor lookup(Iterable<OptionDescriptors> loader, String name) { 118 for (OptionDescriptors optionDescriptors : loader) { 119 OptionDescriptor desc = optionDescriptors.get(name); 120 if (desc != null) { 121 return desc; 122 } 123 } 124 return null; 125 } 126 127 /** 128 * Parses a given option name and value. 129 * 130 * @param name the option name 131 * @param uncheckedValue the unchecked value for the option 132 * @param values the object in which to store the parsed option and value 133 * @param loader source of the available {@link OptionDescriptors} 134 * @throws IllegalArgumentException if there's a problem parsing {@code option} 135 */ 136 public static void parseOption(String name, Object uncheckedValue, EconomicMap<OptionKey<?>, Object> values, Iterable<OptionDescriptors> loader) { 137 138 OptionDescriptor desc = lookup(loader, name); 139 if (desc == null) { 140 List<OptionDescriptor> matches = fuzzyMatch(loader, name); 141 Formatter msg = new Formatter(); 142 msg.format("Could not find option %s", name); 143 if (!matches.isEmpty()) { 144 msg.format("%nDid you mean one of the following?"); 145 for (OptionDescriptor match : matches) { 146 msg.format("%n %s=<value>", match.getName()); 147 } 148 } 149 throw new IllegalArgumentException(msg.toString()); 150 } 151 152 Class<?> optionType = desc.getOptionValueType(); 153 Object value; 154 if (!(uncheckedValue instanceof String)) { 155 if (optionType != uncheckedValue.getClass()) { 156 String type = optionType.getSimpleName(); 157 throw new IllegalArgumentException(type + " option '" + name + "' must have " + type + " value, not " + uncheckedValue.getClass() + " [toString: " + uncheckedValue + "]"); 158 } 159 value = uncheckedValue; 160 } else { 161 String valueString = (String) uncheckedValue; 162 if (optionType == Boolean.class) { 163 if ("true".equals(valueString)) { 164 value = Boolean.TRUE; 165 } else if ("false".equals(valueString)) { 166 value = Boolean.FALSE; 167 } else { 168 throw new IllegalArgumentException("Boolean option '" + name + "' must have value \"true\" or \"false\", not \"" + uncheckedValue + "\""); 169 } 170 } else if (optionType == String.class) { 171 value = valueString; 172 } else if (Enum.class.isAssignableFrom(optionType)) { 173 value = ((EnumOptionKey<?>) desc.getOptionKey()).valueOf(valueString); 174 } else { 175 if (valueString.isEmpty()) { 176 throw new IllegalArgumentException("Non empty value required for option '" + name + "'"); 177 } 178 try { 179 if (optionType == Float.class) { 180 value = Float.parseFloat(valueString); 181 } else if (optionType == Double.class) { 182 value = Double.parseDouble(valueString); 183 } else if (optionType == Integer.class) { 184 value = Integer.valueOf((int) parseLong(valueString)); 185 } else if (optionType == Long.class) { 186 value = Long.valueOf(parseLong(valueString)); 187 } else { 188 throw new IllegalArgumentException("Wrong value for option '" + name + "'"); 189 } 190 } catch (NumberFormatException nfe) { 191 throw new IllegalArgumentException("Value for option '" + name + "' has invalid number format: " + valueString); 192 } 193 } 194 } 195 196 desc.getOptionKey().update(values, value); 197 } 198 199 private static long parseLong(String v) { 200 String valueString = v.toLowerCase(); 201 long scale = 1; 202 if (valueString.endsWith("k")) { 203 scale = 1024L; 204 } else if (valueString.endsWith("m")) { 205 scale = 1024L * 1024L; 206 } else if (valueString.endsWith("g")) { 207 scale = 1024L * 1024L * 1024L; 208 } else if (valueString.endsWith("t")) { 209 scale = 1024L * 1024L * 1024L * 1024L; 210 } 211 212 if (scale != 1) { 213 /* Remove trailing scale character. */ 214 valueString = valueString.substring(0, valueString.length() - 1); 215 } 216 217 return Long.parseLong(valueString) * scale; 218 } 219 220 /** 221 * Compute string similarity based on Dice's coefficient. 222 * 223 * Ported from str_similar() in globals.cpp. 224 */ 225 static float stringSimiliarity(String str1, String str2) { 226 int hit = 0; 227 for (int i = 0; i < str1.length() - 1; ++i) { 228 for (int j = 0; j < str2.length() - 1; ++j) { 229 if ((str1.charAt(i) == str2.charAt(j)) && (str1.charAt(i + 1) == str2.charAt(j + 1))) { 230 ++hit; 231 break; 232 } 233 } 234 } 235 return 2.0f * hit / (str1.length() + str2.length()); 236 } 237 238 private static final float FUZZY_MATCH_THRESHOLD = 0.7F; 239 240 /** 241 * Returns the set of options that fuzzy match a given option name. 242 */ 243 private static List<OptionDescriptor> fuzzyMatch(Iterable<OptionDescriptors> loader, String optionName) { 244 List<OptionDescriptor> matches = new ArrayList<>(); 245 for (OptionDescriptors options : loader) { 246 collectFuzzyMatches(options, optionName, matches); 247 } 248 return matches; 249 } 250 251 /** 252 * Collects the set of options that fuzzy match a given option name. String similarity for fuzzy 253 * matching is based on Dice's coefficient. 254 * 255 * @param toSearch the set of option descriptors to search 256 * @param name the option name to search for 257 * @param matches the collection to which fuzzy matches of {@code name} will be added 258 * @return whether any fuzzy matches were found 259 */ 260 public static boolean collectFuzzyMatches(Iterable<OptionDescriptor> toSearch, String name, Collection<OptionDescriptor> matches) { 261 boolean found = false; 262 for (OptionDescriptor option : toSearch) { 263 float score = stringSimiliarity(option.getName(), name); 264 if (score >= FUZZY_MATCH_THRESHOLD) { 265 found = true; 266 matches.add(option); 267 } 268 } 269 return found; 270 } 271 }