From bdcec3fd54816ec82e549e7d015480e95e678866 Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Thu, 2 Nov 2023 10:39:08 +0100 Subject: [PATCH 1/4] Show migration messages across hosted options. --- .../com/oracle/svm/core/SubstrateOptions.java | 2 +- .../core/configure/ConfigurationFiles.java | 35 +++++++++----- .../doc-files/ProxyConfigurationFilesHelp.txt | 2 + .../ReflectionConfigurationFilesHelp.txt | 2 + .../SerializationConfigurationFilesHelp.txt | 2 + .../oracle/svm/hosted/ProgressReporter.java | 48 ++++++++++++------- .../oracle/svm/hosted/ResourcesFeature.java | 3 +- 7 files changed, 62 insertions(+), 32 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 4552769172a0..06366f2c1ae8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -340,7 +340,7 @@ public static void setOptimizeValueUpdateHandler(ValueUpdateHandler LinkerRPath = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @Option(help = "Directory of the image file to be generated", type = OptionType.User)// + @Option(help = {"Directory of the image file to be generated", "Use the '-o' option instead."}, type = OptionType.User)// public static final HostedOptionKey Path = new HostedOptionKey<>(null); public static final class GCGroup implements APIOptionGroup { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index 8b3d0235878e..ff7323be53a6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -60,19 +60,22 @@ public static final class Options { @Option(help = "file:doc-files/ReflectionConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey ReflectionConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing program elements to be made available for reflection (see ReflectionConfigurationFiles).", type = OptionType.User)// + @Option(help = {"Resources describing program elements to be made available for reflection (see ReflectionConfigurationFiles).", + "Use a reflect-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// public static final HostedOptionKey ReflectionConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "file:doc-files/ProxyConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey DynamicProxyConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing program elements to be made available for reflection (see ProxyConfigurationFiles).", type = OptionType.User)// + @Option(help = {"Resources describing program elements to be made available for reflection (see ProxyConfigurationFiles).", + "Use a proxy-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// public static final HostedOptionKey DynamicProxyConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "file:doc-files/SerializationConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey SerializationConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing program elements to be made available for serialization (see SerializationConfigurationFiles).", type = OptionType.User)// + @Option(help = {"Resources describing program elements to be made available for serialization (see SerializationConfigurationFiles).", + "Use a serialization-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// public static final HostedOptionKey SerializationConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "file:doc-files/SerializationConfigurationFilesHelp.txt", type = OptionType.User)// @@ -82,18 +85,22 @@ public static final class Options { public static final HostedOptionKey SerializationDenyConfigurationResources = new HostedOptionKey<>( LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @Option(help = "Files describing Java resources to be included in the image according to the schema at " + - "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", type = OptionType.User)// + @Option(help = {"Files describing Java resources to be included in the image according to the schema at " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", + "Use a resource-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey ResourceConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing Java resources to be included in the image according to the schema at " + - "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", type = OptionType.User)// + @Option(help = {"Resources describing Java resources to be included in the image according to the schema at " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", + "Use a resource-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// public static final HostedOptionKey ResourceConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @Option(help = "Files describing program elements to be made accessible via JNI (for syntax, see ReflectionConfigurationFiles)", type = OptionType.User)// + @Option(help = {"Files describing program elements to be made accessible via JNI (for syntax, see ReflectionConfigurationFiles)", + "Use a jni-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey JNIConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing program elements to be made accessible via JNI (see JNIConfigurationFiles).", type = OptionType.User)// + @Option(help = {"Resources describing program elements to be made accessible via JNI (see JNIConfigurationFiles).", + "Use a jni-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// public static final HostedOptionKey JNIConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Files describing stubs allowing foreign calls.", type = OptionType.User)// @@ -102,12 +109,14 @@ public static final class Options { @Option(help = "Resources describing stubs allowing foreign calls.", type = OptionType.User)// public static final HostedOptionKey ForeignResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @Option(help = "Files describing predefined classes that can be loaded at runtime according to the schema at " + - "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", type = OptionType.User)// + @Option(help = {"Files describing predefined classes that can be loaded at runtime according to the schema at " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", + "Use a predefined-classes-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey PredefinedClassesConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing predefined classes that can be loaded at runtime according to the schema at " + - "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", type = OptionType.User)// + @Option(help = {"Resources describing predefined classes that can be loaded at runtime according to the schema at " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", + "Use a predefined-classes-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// public static final HostedOptionKey PredefinedClassesConfigurationResources = new HostedOptionKey<>( LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt index cf0217313abe..a8144f0d4a3a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt @@ -1,4 +1,6 @@ One or several (comma-separated) paths to JSON files that specify lists of interfaces that define Java proxy classes. +Use a proxy-config.json in your META-INF/native-image// directory instead. + The JSON structure is described in the following schema: https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/proxy-config-schema-v1.0.0.json diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt index aaf1027fdb4b..f55641cebe0a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt @@ -1,4 +1,6 @@ One or several (comma-separated) paths to JSON files that specify which program elements should be made available via reflection. +Use a reflect-config.json in your META-INF/native-image// directory instead. + The JSON object schema is described at: https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.0.0.json diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt index e199d657abc5..e3116ec4a51b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt @@ -1,4 +1,6 @@ One or several (comma-separated) paths to JSON files that specify lists of serialization configurations. +Use a serialization-config.json in your META-INF/native-image// directory instead. + The structure is described in the following schema: https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/serialization-config-schema-v1.0.0.json diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index 1c2bb3df82e1..5c7abde45b37 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -51,13 +51,13 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -import com.oracle.graal.pointsto.meta.AnalysisType; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.impl.ImageSingletonsSupport; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.reports.ReportUtils; import com.oracle.graal.pointsto.util.Timer; @@ -93,6 +93,7 @@ import com.oracle.svm.hosted.util.VMErrorReporter; import com.oracle.svm.util.ImageBuildStatistics; +import jdk.graal.compiler.options.OptionDescriptor; import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionStability; import jdk.graal.compiler.options.OptionValues; @@ -284,22 +285,28 @@ private static void printFeature(DirectPrinter printer, Feature feature) { printer.println(); } - record ExperimentalOptionDetails(String alternatives, String origins) { + record ExperimentalOptionDetails(String migrationMessage, String alternatives, String origins) { public String toSuffix() { - if (alternatives.isEmpty() && origins.isEmpty()) { + if (migrationMessage.isEmpty() && alternatives.isEmpty() && origins.isEmpty()) { return ""; } - StringBuilder sb = new StringBuilder(" ("); - if (!alternatives.isEmpty()) { - sb.append("alternative API option(s): ").append(alternatives); + StringBuilder sb = new StringBuilder(); + if (!migrationMessage.isEmpty()) { + sb.append(": ").append(migrationMessage); } - if (!origins.isEmpty()) { + if (!alternatives.isEmpty() || !origins.isEmpty()) { + sb.append(" ("); if (!alternatives.isEmpty()) { - sb.append("; "); + sb.append("alternative API option(s): ").append(alternatives); + } + if (!origins.isEmpty()) { + if (!alternatives.isEmpty()) { + sb.append("; "); + } + sb.append("origin(s): ").append(origins); } - sb.append("origin(s): ").append(origins); + sb.append(")"); } - sb.append(")"); return sb.toString(); } } @@ -324,8 +331,11 @@ private void printExperimentalOptions(ImageClassLoader classLoader) { continue; } if (option instanceof HostedOptionKey hok && option.getDescriptor().getStability() == OptionStability.EXPERIMENTAL) { + OptionDescriptor hokDescriptor = hok.getDescriptor(); String optionPrefix = hostedOptionPrefix; String origins = ""; + /* We use the first extra help item for migration messages for options. */ + String migrationMessage = hokDescriptor.getExtraHelp().isEmpty() ? "" : hokDescriptor.getExtraHelp().getFirst(); String alternatives = ""; Object value = option.getValueOrDefault(hostedOptionValues); if (value instanceof LocatableMultiOptionValue lmov) { @@ -333,8 +343,10 @@ private void printExperimentalOptions(ImageClassLoader classLoader) { continue; } else { origins = lmov.getValuesWithOrigins().filter(p -> !isStableOrInternalOrigin(p.getRight())).map(p -> p.getRight().toString()).collect(Collectors.joining(", ")); - alternatives = lmov.getValuesWithOrigins().map(p -> SubstrateOptionsParser.commandArgument(hok, p.getLeft().toString())).filter(c -> !c.startsWith(hostedOptionPrefix)) - .collect(Collectors.joining(", ")); + if (alternatives.isEmpty()) { + alternatives = lmov.getValuesWithOrigins().map(p -> SubstrateOptionsParser.commandArgument(hok, p.getLeft().toString())).filter(c -> !c.startsWith(hostedOptionPrefix)) + .collect(Collectors.joining(", ")); + } } } else { OptionOrigin origin = hok.getLastOrigin(); @@ -343,20 +355,22 @@ private void printExperimentalOptions(ImageClassLoader classLoader) { } origins = origin.toString(); String valueString; - if (hok.getDescriptor().getOptionValueType() == Boolean.class) { + if (hokDescriptor.getOptionValueType() == Boolean.class) { valueString = Boolean.parseBoolean(value.toString()) ? "+" : "-"; optionPrefix += valueString; } else { valueString = value.toString(); } - String command = SubstrateOptionsParser.commandArgument(hok, valueString); - if (!command.startsWith(hostedOptionPrefix)) { - alternatives = command; + if (alternatives.isEmpty()) { + String command = SubstrateOptionsParser.commandArgument(hok, valueString); + if (!command.startsWith(hostedOptionPrefix)) { + alternatives = command; + } } } String rawHostedOptionName = optionPrefix + hok.getName(); if (rawHostedOptionNamesFromDriver.contains(rawHostedOptionName)) { - experimentalOptions.put(rawHostedOptionName, new ExperimentalOptionDetails(alternatives, origins)); + experimentalOptions.put(rawHostedOptionName, new ExperimentalOptionDetails(migrationMessage, alternatives, origins)); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java index f46a8f9c3d77..91a98821fe92 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java @@ -138,7 +138,8 @@ public final class ResourcesFeature implements InternalFeature { static final String MODULE_NAME_ALL_UNNAMED = "ALL-UNNAMED"; public static class Options { - @Option(help = "Regexp to match names of resources to be included in the image.", type = OptionType.User)// + @Option(help = {"Regexp to match names of resources to be included in the image.", + "Use a resource-config.json in your META-INF/native-image// directory instead."}, type = OptionType.User)// public static final HostedOptionKey IncludeResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Regexp to match names of resources to be excluded from the image.", type = OptionType.User)// From a52ea877b252486e5d68bdb6baa1375c1cc0d630 Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Thu, 2 Nov 2023 13:27:33 +0100 Subject: [PATCH 2/4] Driver disables watchdog when `--debug-attach` is used. --- .../src/com/oracle/svm/driver/CmdLineOptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java index 2d7d6ad67d03..4914b9e2db6f 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java @@ -167,7 +167,7 @@ private boolean consume(ArgumentQueue args, String headArg) { /* Using agentlib to allow interoperability with other agents */ nativeImage.addImageBuilderJavaArgs("-agentlib:jdwp=transport=dt_socket,server=y,address=" + address + ",suspend=y"); /* Disable watchdog mechanism */ - nativeImage.addPlainImageBuilderArg(nativeImage.oHDeadlockWatchdogInterval + "0"); + nativeImage.addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(nativeImage.oHDeadlockWatchdogInterval + "0", OptionOrigin.originDriver)); return true; } From 48a2085504236f254cd07c4033d6248d59bdc407 Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Mon, 6 Nov 2023 10:36:32 +0100 Subject: [PATCH 3/4] Make `commandArgument()` more robust. --- .../svm/core/option/SubstrateOptionsParser.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java index 5c2151bddc45..ba9a1991e850 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java @@ -33,9 +33,6 @@ import java.util.function.Predicate; import org.graalvm.collections.EconomicMap; -import jdk.graal.compiler.options.OptionDescriptor; -import jdk.graal.compiler.options.OptionDescriptors; -import jdk.graal.compiler.options.OptionKey; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -47,6 +44,10 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.LogUtils; +import jdk.graal.compiler.options.OptionDescriptor; +import jdk.graal.compiler.options.OptionDescriptors; +import jdk.graal.compiler.options.OptionKey; + /** * This class contains methods for parsing options and matching them against * {@link OptionDescriptor}s. @@ -178,14 +179,18 @@ public static String commandArgument(OptionKey option, String value, String a /* Ensure descriptor is loaded */ OptionDescriptor optionDescriptor = option.loadDescriptor(); Field field; + APIOption[] apiOptions; try { field = optionDescriptor.getDeclaringClass().getDeclaredField(optionDescriptor.getFieldName()); + apiOptions = field.getAnnotationsByType(APIOption.class); } catch (ReflectiveOperationException ex) { - throw VMError.shouldNotReachHere(ex); + /* + * Options whose fields cannot be looked up (e.g., due to stripped sources) cannot be + * API options by definition. + */ + apiOptions = new APIOption[0]; } - APIOption[] apiOptions = field.getAnnotationsByType(APIOption.class); - if (optionDescriptor.getOptionValueType() == Boolean.class) { VMError.guarantee(value.equals("+") || value.equals("-"), "Boolean option value can be only + or -"); for (APIOption apiOption : apiOptions) { From 6b9173ab1b9c57a644757fdba28fecf8f895c064 Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Thu, 2 Nov 2023 13:31:50 +0100 Subject: [PATCH 4/4] Revise experimental options printing. This also make sure that `-H:Log` and other `OptionKey`s that are not `HostedOptionKey`s are listed. --- .../oracle/svm/hosted/ProgressReporter.java | 116 ++++++++++-------- 1 file changed, 65 insertions(+), 51 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index 5c7abde45b37..3399c5ec69c3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -35,13 +35,11 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.Executors; @@ -76,6 +74,7 @@ import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.option.LocatableMultiOptionValue; import com.oracle.svm.core.option.OptionOrigin; +import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.util.json.JsonWriter; @@ -312,74 +311,89 @@ public String toSuffix() { } private void printExperimentalOptions(ImageClassLoader classLoader) { - String hostedOptionPrefix = CommonOptionParser.HOSTED_OPTION_PREFIX; - - Set rawHostedOptionNamesFromDriver = new HashSet<>(); + /* + * Step 1: scan all builder arguments and collect relevant options. + */ + Map experimentalBuilderOptionsAndOrigins = new HashMap<>(); for (String arg : DiagnosticUtils.getBuilderArguments(classLoader)) { - if (!arg.startsWith(hostedOptionPrefix)) { + if (!arg.startsWith(CommonOptionParser.HOSTED_OPTION_PREFIX)) { continue; } - String rawOption = arg.split("=", 2)[0].split("@", 2)[0]; - rawHostedOptionNamesFromDriver.add(rawOption); + String[] optionParts = arg.split("=", 2)[0].split("@", 2); + OptionOrigin optionOrigin = optionParts.length == 2 ? OptionOrigin.from(optionParts[1], false) : null; + if (optionOrigin == null || !isStableOrInternalOrigin(optionOrigin)) { + String prefixedOptionName = optionParts[0]; + experimentalBuilderOptionsAndOrigins.put(prefixedOptionName, optionOrigin); + } } - + if (experimentalBuilderOptionsAndOrigins.isEmpty()) { + return; + } + /* + * Step 2: scan HostedOptionValues and collect migrationMessage, alternatives, and origins. + */ Map experimentalOptions = new HashMap<>(); var hostedOptionValues = HostedOptionValues.singleton().getMap(); - for (OptionKey option : hostedOptionValues.getKeys()) { - if (option == SubstrateOptions.UnlockExperimentalVMOptions) { + if (option instanceof RuntimeOptionKey || option == SubstrateOptions.UnlockExperimentalVMOptions || option.getDescriptor().getStability() != OptionStability.EXPERIMENTAL) { continue; } - if (option instanceof HostedOptionKey hok && option.getDescriptor().getStability() == OptionStability.EXPERIMENTAL) { - OptionDescriptor hokDescriptor = hok.getDescriptor(); - String optionPrefix = hostedOptionPrefix; - String origins = ""; - /* We use the first extra help item for migration messages for options. */ - String migrationMessage = hokDescriptor.getExtraHelp().isEmpty() ? "" : hokDescriptor.getExtraHelp().getFirst(); - String alternatives = ""; - Object value = option.getValueOrDefault(hostedOptionValues); - if (value instanceof LocatableMultiOptionValue lmov) { - if (lmov.getValuesWithOrigins().allMatch(o -> o.getRight().isStable())) { - continue; - } else { - origins = lmov.getValuesWithOrigins().filter(p -> !isStableOrInternalOrigin(p.getRight())).map(p -> p.getRight().toString()).collect(Collectors.joining(", ")); - if (alternatives.isEmpty()) { - alternatives = lmov.getValuesWithOrigins().map(p -> SubstrateOptionsParser.commandArgument(hok, p.getLeft().toString())).filter(c -> !c.startsWith(hostedOptionPrefix)) - .collect(Collectors.joining(", ")); - } - } + OptionDescriptor descriptor = option.getDescriptor(); + Object optionValue = option.getValueOrDefault(hostedOptionValues); + String emptyOrBooleanValue = ""; + if (descriptor.getOptionValueType() == Boolean.class) { + emptyOrBooleanValue = Boolean.parseBoolean(optionValue.toString()) ? "+" : "-"; + } + String prefixedOptionName = CommonOptionParser.HOSTED_OPTION_PREFIX + emptyOrBooleanValue + option.getName(); + if (!experimentalBuilderOptionsAndOrigins.containsKey(prefixedOptionName)) { + /* Only check builder arguments, ignore options that were set as part of others. */ + continue; + } + String origins = ""; + /* The first extra help item is used for migration messages of options. */ + String migrationMessage = descriptor.getExtraHelp().isEmpty() ? "" : descriptor.getExtraHelp().getFirst(); + String alternatives = ""; + + if (optionValue instanceof LocatableMultiOptionValue lmov) { + if (lmov.getValuesWithOrigins().allMatch(o -> o.getRight().isStable())) { + continue; } else { - OptionOrigin origin = hok.getLastOrigin(); - if (origin == null /* unknown */ || isStableOrInternalOrigin(origin)) { - continue; - } - origins = origin.toString(); - String valueString; - if (hokDescriptor.getOptionValueType() == Boolean.class) { - valueString = Boolean.parseBoolean(value.toString()) ? "+" : "-"; - optionPrefix += valueString; - } else { - valueString = value.toString(); - } - if (alternatives.isEmpty()) { - String command = SubstrateOptionsParser.commandArgument(hok, valueString); - if (!command.startsWith(hostedOptionPrefix)) { - alternatives = command; - } - } + origins = lmov.getValuesWithOrigins().filter(p -> !isStableOrInternalOrigin(p.getRight())).map(p -> p.getRight().toString()).collect(Collectors.joining(", ")); + alternatives = lmov.getValuesWithOrigins().map(p -> SubstrateOptionsParser.commandArgument(option, p.getLeft().toString())) + .filter(c -> !c.startsWith(CommonOptionParser.HOSTED_OPTION_PREFIX)) + .collect(Collectors.joining(", ")); + } + } else { + OptionOrigin origin = experimentalBuilderOptionsAndOrigins.get(prefixedOptionName); + if (origin == null && option instanceof HostedOptionKey hok) { + origin = hok.getLastOrigin(); + } + if (origin == null /* unknown */ || isStableOrInternalOrigin(origin)) { + continue; + } + origins = origin.toString(); + String optionValueString; + if (descriptor.getOptionValueType() == Boolean.class) { + assert !emptyOrBooleanValue.isEmpty(); + optionValueString = emptyOrBooleanValue; + } else { + optionValueString = String.valueOf(optionValue); } - String rawHostedOptionName = optionPrefix + hok.getName(); - if (rawHostedOptionNamesFromDriver.contains(rawHostedOptionName)) { - experimentalOptions.put(rawHostedOptionName, new ExperimentalOptionDetails(migrationMessage, alternatives, origins)); + String command = SubstrateOptionsParser.commandArgument(option, optionValueString); + if (!command.startsWith(CommonOptionParser.HOSTED_OPTION_PREFIX)) { + alternatives = command; } } + experimentalOptions.put(prefixedOptionName, new ExperimentalOptionDetails(migrationMessage, alternatives, origins)); } + /* + * Step 3: print list of experimental options (if any). + */ if (experimentalOptions.isEmpty()) { return; } l().printLineSeparator(); l().yellowBold().a(" ").a(experimentalOptions.size()).a(" ").doclink("experimental option(s)", "#glossary-experimental-options").a(" unlocked").reset().a(":").println(); - for (var optionAndDetails : experimentalOptions.entrySet()) { l().a(" - '%s'%s", optionAndDetails.getKey(), optionAndDetails.getValue().toSuffix()).println(); }