Skip to content

[GR-63551] Improve build-time validation of Native Image options. #10940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 29, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -39,13 +39,13 @@
/** Common options that can be specified for both the serial and the epsilon GC. */
public final class SerialAndEpsilonGCOptions {
@Option(help = "The maximum heap size as percent of physical memory. Serial and epsilon GC only.", type = OptionType.User) //
public static final RuntimeOptionKey<Integer> MaximumHeapSizePercent = new NotifyGCRuntimeOptionKey<>(80, SerialAndEpsilonGCOptions::serialOrEpsilonGCOnly);
public static final RuntimeOptionKey<Integer> MaximumHeapSizePercent = new NotifyGCRuntimeOptionKey<>(80, SerialAndEpsilonGCOptions::validateSerialOrEpsilonRuntimeOption);

@Option(help = "The maximum size of the young generation as a percentage of the maximum heap size. Serial and epsilon GC only.", type = OptionType.User) //
public static final RuntimeOptionKey<Integer> MaximumYoungGenerationSizePercent = new NotifyGCRuntimeOptionKey<>(10, SerialAndEpsilonGCOptions::serialOrEpsilonGCOnly);
public static final RuntimeOptionKey<Integer> MaximumYoungGenerationSizePercent = new NotifyGCRuntimeOptionKey<>(10, SerialAndEpsilonGCOptions::validateSerialOrEpsilonRuntimeOption);

@Option(help = "The size of an aligned chunk. Serial and epsilon GC only.", type = OptionType.Expert) //
public static final HostedOptionKey<Long> AlignedHeapChunkSize = new HostedOptionKey<>(512 * 1024L, SerialAndEpsilonGCOptions::serialOrEpsilonGCOnly) {
public static final HostedOptionKey<Long> AlignedHeapChunkSize = new HostedOptionKey<>(512 * 1024L, SerialAndEpsilonGCOptions::validateSerialOrEpsilonHostedOption) {
@Override
protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Long oldValue, Long newValue) {
int multiple = 4096;
@@ -58,25 +58,31 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Long oldV
* fit in an aligned chunk.
*/
@Option(help = "The size at or above which an array will be allocated in its own unaligned chunk. Serial and epsilon GC only.", type = OptionType.Expert) //
public static final HostedOptionKey<Long> LargeArrayThreshold = new HostedOptionKey<>(128 * 1024L, SerialAndEpsilonGCOptions::serialOrEpsilonGCOnly);
public static final HostedOptionKey<Long> LargeArrayThreshold = new HostedOptionKey<>(128 * 1024L, SerialAndEpsilonGCOptions::validateSerialOrEpsilonHostedOption);

@Option(help = "Fill unused memory chunks with a sentinel value. Serial and epsilon GC only.", type = OptionType.Debug) //
public static final HostedOptionKey<Boolean> ZapChunks = new HostedOptionKey<>(false, SerialAndEpsilonGCOptions::serialOrEpsilonGCOnly);
public static final HostedOptionKey<Boolean> ZapChunks = new HostedOptionKey<>(false, SerialAndEpsilonGCOptions::validateSerialOrEpsilonHostedOption);

@Option(help = "Before use, fill memory chunks with a sentinel value. Serial and epsilon GC only.", type = OptionType.Debug) //
public static final HostedOptionKey<Boolean> ZapProducedHeapChunks = new HostedOptionKey<>(false, SerialAndEpsilonGCOptions::serialOrEpsilonGCOnly);
public static final HostedOptionKey<Boolean> ZapProducedHeapChunks = new HostedOptionKey<>(false, SerialAndEpsilonGCOptions::validateSerialOrEpsilonHostedOption);

@Option(help = "After use, Fill memory chunks with a sentinel value. Serial and epsilon GC only.", type = OptionType.Debug) //
public static final HostedOptionKey<Boolean> ZapConsumedHeapChunks = new HostedOptionKey<>(false, SerialAndEpsilonGCOptions::serialOrEpsilonGCOnly);
public static final HostedOptionKey<Boolean> ZapConsumedHeapChunks = new HostedOptionKey<>(false, SerialAndEpsilonGCOptions::validateSerialOrEpsilonHostedOption);

@Option(help = "Number of bytes at the beginning of each heap chunk that are not used for payload data, i.e., can be freely used as metadata by the heap chunk provider. Serial and epsilon GC only.", type = OptionType.Debug) //
public static final HostedOptionKey<Integer> HeapChunkHeaderPadding = new HostedOptionKey<>(0, SerialAndEpsilonGCOptions::serialOrEpsilonGCOnly);
public static final HostedOptionKey<Integer> HeapChunkHeaderPadding = new HostedOptionKey<>(0, SerialAndEpsilonGCOptions::validateSerialOrEpsilonHostedOption);

private SerialAndEpsilonGCOptions() {
}

public static void serialOrEpsilonGCOnly(OptionKey<?> optionKey) {
if (!SubstrateOptions.useSerialGC() && !SubstrateOptions.useEpsilonGC()) {
public static void validateSerialOrEpsilonHostedOption(HostedOptionKey<?> optionKey) {
if (optionKey.hasBeenSet() && !SubstrateOptions.useSerialGC() && !SubstrateOptions.useEpsilonGC()) {
throw UserError.abort("The option '" + optionKey.getName() + "' can only be used together with the serial ('--gc=serial') or the epsilon garbage collector ('--gc=epsilon').");
}
}

public static void validateSerialOrEpsilonRuntimeOption(RuntimeOptionKey<?> optionKey) {
if (optionKey.hasBeenSet() && !SubstrateOptions.useSerialGC() && !SubstrateOptions.useEpsilonGC()) {
throw UserError.abort("The option '" + optionKey.getName() + "' can only be used together with the serial ('--gc=serial') or the epsilon garbage collector ('--gc=epsilon').");
}
}
Original file line number Diff line number Diff line change
@@ -43,16 +43,16 @@
/** Options that are only valid for the serial GC (and not for the epsilon GC). */
public final class SerialGCOptions {
@Option(help = "The garbage collection policy, either Adaptive (default) or BySpaceAndTime. Serial GC only.", type = OptionType.User)//
public static final HostedOptionKey<String> InitialCollectionPolicy = new HostedOptionKey<>("Adaptive", SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<String> InitialCollectionPolicy = new HostedOptionKey<>("Adaptive", SerialGCOptions::validateSerialHostedOption);

@Option(help = "Percentage of total collection time that should be spent on young generation collections. Serial GC with collection policy 'BySpaceAndTime' only.", type = OptionType.User)//
public static final RuntimeOptionKey<Integer> PercentTimeInIncrementalCollection = new RuntimeOptionKey<>(50, SerialGCOptions::serialGCOnly);
public static final RuntimeOptionKey<Integer> PercentTimeInIncrementalCollection = new RuntimeOptionKey<>(50, SerialGCOptions::validateSerialRuntimeOption);

@Option(help = "The maximum free bytes reserved for allocations, in bytes (0 for automatic according to GC policy). Serial GC only.", type = OptionType.User)//
public static final RuntimeOptionKey<Long> MaxHeapFree = new RuntimeOptionKey<>(0L, SerialGCOptions::serialGCOnly);
public static final RuntimeOptionKey<Long> MaxHeapFree = new RuntimeOptionKey<>(0L, SerialGCOptions::validateSerialRuntimeOption);

@Option(help = "Maximum number of survivor spaces. Serial GC only.", type = OptionType.Expert) //
public static final HostedOptionKey<Integer> MaxSurvivorSpaces = new HostedOptionKey<>(null, SerialGCOptions::serialGCOnly) {
public static final HostedOptionKey<Integer> MaxSurvivorSpaces = new HostedOptionKey<>(null, SerialGCOptions::validateSerialHostedOption) {
@Override
public Integer getValueOrDefault(UnmodifiableEconomicMap<OptionKey<?>, Object> values) {
Integer value = (Integer) values.get(this);
@@ -68,67 +68,67 @@ public Integer getValue(OptionValues values) {
};

@Option(help = "Determines if a full GC collects the young generation separately or together with the old generation. Serial GC only.", type = OptionType.Expert) //
public static final RuntimeOptionKey<Boolean> CollectYoungGenerationSeparately = new RuntimeOptionKey<>(null, SerialGCOptions::serialGCOnly);
public static final RuntimeOptionKey<Boolean> CollectYoungGenerationSeparately = new RuntimeOptionKey<>(null, SerialGCOptions::validateSerialRuntimeOption);

@Option(help = "Enables card marking for image heap objects, which arranges them in chunks. Automatically enabled when supported. Serial GC only.", type = OptionType.Expert) //
public static final HostedOptionKey<Boolean> ImageHeapCardMarking = new HostedOptionKey<>(null, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> ImageHeapCardMarking = new HostedOptionKey<>(null, SerialGCOptions::validateSerialHostedOption);

@Option(help = "This number of milliseconds multiplied by the free heap memory in MByte is the time span " +
"for which a soft reference will keep its referent alive after its last access. Serial GC only.", type = OptionType.Expert) //
public static final HostedOptionKey<Integer> SoftRefLRUPolicyMSPerMB = new HostedOptionKey<>(1000, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Integer> SoftRefLRUPolicyMSPerMB = new HostedOptionKey<>(1000, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Print summary GC information after application main method returns. Serial GC only.", type = OptionType.Debug)//
public static final RuntimeOptionKey<Boolean> PrintGCSummary = new RuntimeOptionKey<>(false, SerialGCOptions::serialGCOnly);
public static final RuntimeOptionKey<Boolean> PrintGCSummary = new RuntimeOptionKey<>(false, SerialGCOptions::validateSerialRuntimeOption);

@Option(help = "Print the time for each of the phases of each collection, if +VerboseGC. Serial GC only.", type = OptionType.Debug)//
public static final RuntimeOptionKey<Boolean> PrintGCTimes = new RuntimeOptionKey<>(false, SerialGCOptions::serialGCOnly);
public static final RuntimeOptionKey<Boolean> PrintGCTimes = new RuntimeOptionKey<>(false, SerialGCOptions::validateSerialRuntimeOption);

@Option(help = "Verify the heap before doing a garbage collection if VerifyHeap is enabled. Serial GC only.", type = OptionType.Debug)//
public static final HostedOptionKey<Boolean> VerifyBeforeGC = new HostedOptionKey<>(true, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> VerifyBeforeGC = new HostedOptionKey<>(true, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Verify the heap during a garbage collection if VerifyHeap is enabled. Serial GC only.", type = OptionType.Debug)//
public static final HostedOptionKey<Boolean> VerifyDuringGC = new HostedOptionKey<>(true, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> VerifyDuringGC = new HostedOptionKey<>(true, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Verify the heap after doing a garbage collection if VerifyHeap is enabled. Serial GC only.", type = OptionType.Debug)//
public static final HostedOptionKey<Boolean> VerifyAfterGC = new HostedOptionKey<>(true, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> VerifyAfterGC = new HostedOptionKey<>(true, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Verify the remembered set if VerifyHeap is enabled. Serial GC only.", type = OptionType.Debug)//
public static final HostedOptionKey<Boolean> VerifyRememberedSet = new HostedOptionKey<>(true, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> VerifyRememberedSet = new HostedOptionKey<>(true, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Verify all object references if VerifyHeap is enabled. Serial GC only.", type = OptionType.Debug)//
public static final HostedOptionKey<Boolean> VerifyReferences = new HostedOptionKey<>(true, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> VerifyReferences = new HostedOptionKey<>(true, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Verify that object references point into valid heap chunks if VerifyHeap is enabled. Serial GC only.", type = OptionType.Debug)//
public static final HostedOptionKey<Boolean> VerifyReferencesPointIntoValidChunk = new HostedOptionKey<>(false, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> VerifyReferencesPointIntoValidChunk = new HostedOptionKey<>(false, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Verify write barriers. Serial GC only.", type = OptionType.Debug)//
public static final HostedOptionKey<Boolean> VerifyWriteBarriers = new HostedOptionKey<>(false, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> VerifyWriteBarriers = new HostedOptionKey<>(false, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Trace heap chunks during collections, if +VerboseGC. Serial GC only.", type = OptionType.Debug) //
public static final RuntimeOptionKey<Boolean> TraceHeapChunks = new RuntimeOptionKey<>(false, SerialGCOptions::serialGCOnly);
public static final RuntimeOptionKey<Boolean> TraceHeapChunks = new RuntimeOptionKey<>(false, SerialGCOptions::validateSerialRuntimeOption);

@Option(help = "Develop demographics of the object references visited. Serial GC only.", type = OptionType.Debug)//
public static final HostedOptionKey<Boolean> GreyToBlackObjRefDemographics = new HostedOptionKey<>(false, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> GreyToBlackObjRefDemographics = new HostedOptionKey<>(false, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Ignore the maximum heap size while in VM-internal code. Serial GC only.", type = OptionType.Expert)//
public static final HostedOptionKey<Boolean> IgnoreMaxHeapSizeWhileInVMInternalCode = new HostedOptionKey<>(false, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> IgnoreMaxHeapSizeWhileInVMInternalCode = new HostedOptionKey<>(false, SerialGCOptions::validateSerialHostedOption);

@Option(help = "Determines whether to always (if true) or never (if false) outline write barrier code to a separate function, " +
"trading reduced image size for (potentially) worse performance. Serial GC only.", type = OptionType.Expert) //
public static final HostedOptionKey<Boolean> OutlineWriteBarriers = new HostedOptionKey<>(null, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> OutlineWriteBarriers = new HostedOptionKey<>(null, SerialGCOptions::validateSerialHostedOption);

/** Query these options only through an appropriate method. */
public static class ConcealedOptions {
@Option(help = "Collect old generation by compacting in-place instead of copying. Serial GC only.", type = OptionType.Expert) //
public static final HostedOptionKey<Boolean> CompactingOldGen = new HostedOptionKey<>(false, SerialGCOptions::validateCompactingOldGen);

@Option(help = "Determines if a remembered set is used, which is necessary for collecting the young and old generation independently. Serial GC only.", type = OptionType.Expert) //
public static final HostedOptionKey<Boolean> UseRememberedSet = new HostedOptionKey<>(true, SerialGCOptions::serialGCOnly);
public static final HostedOptionKey<Boolean> UseRememberedSet = new HostedOptionKey<>(true, SerialGCOptions::validateSerialHostedOption);
}

public static class DeprecatedOptions {
@Option(help = "Ignore the maximum heap size while in VM-internal code. Serial GC only.", type = OptionType.Expert, deprecated = true, deprecationMessage = "Please use the option 'IgnoreMaxHeapSizeWhileInVMInternalCode' instead.")//
public static final HostedOptionKey<Boolean> IgnoreMaxHeapSizeWhileInVMOperation = new HostedOptionKey<>(false, SerialGCOptions::serialGCOnly) {
public static final HostedOptionKey<Boolean> IgnoreMaxHeapSizeWhileInVMOperation = new HostedOptionKey<>(false, SerialGCOptions::validateSerialHostedOption) {
@Override
protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean oldValue, Boolean newValue) {
IgnoreMaxHeapSizeWhileInVMInternalCode.update(values, newValue);
@@ -139,8 +139,14 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean o
private SerialGCOptions() {
}

private static void serialGCOnly(OptionKey<?> optionKey) {
if (!SubstrateOptions.useSerialGC()) {
private static void validateSerialHostedOption(HostedOptionKey<?> optionKey) {
if (optionKey.hasBeenSet() && !SubstrateOptions.useSerialGC()) {
throw UserError.abort("The option '" + optionKey.getName() + "' can only be used together with the serial garbage collector ('--gc=serial').");
}
}

private static void validateSerialRuntimeOption(RuntimeOptionKey<?> optionKey) {
if (optionKey.hasBeenSet() && !SubstrateOptions.useSerialGC()) {
throw UserError.abort("The option '" + optionKey.getName() + "' can only be used together with the serial garbage collector ('--gc=serial').");
}
}
@@ -149,7 +155,7 @@ private static void validateCompactingOldGen(HostedOptionKey<Boolean> compacting
if (!compactingOldGen.getValue()) {
return;
}
serialGCOnly(compactingOldGen);
validateSerialHostedOption(compactingOldGen);
if (!useRememberedSet()) {
throw UserError.abort("%s requires %s.", SubstrateOptionsParser.commandArgument(ConcealedOptions.CompactingOldGen, "+"),
SubstrateOptionsParser.commandArgument(ConcealedOptions.UseRememberedSet, "+"));
Original file line number Diff line number Diff line change
@@ -109,7 +109,7 @@ private static boolean isPlatformSupported() {
}

private static void validateSamplerOption(HostedOptionKey<Boolean> isSamplerEnabled) {
if (isSamplerEnabled.getValue()) {
if (isSamplerEnabled.hasBeenSet() && isSamplerEnabled.getValue()) {
UserError.guarantee(isPlatformSupported(),
"The %s cannot be used to profile on this platform.",
SubstrateOptionsParser.commandArgument(isSamplerEnabled, "+"));
Original file line number Diff line number Diff line change
@@ -138,8 +138,13 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean o
@APIOption(name = "static")//
@Option(help = "Build statically linked executable (requires static libc and zlib)")//
public static final HostedOptionKey<Boolean> StaticExecutable = new HostedOptionKey<>(false, key -> {
if (!key.getValue()) {
return;
}

if (!Platform.includedIn(Platform.LINUX.class)) {
throw UserError.invalidOptionValue(key, key.getValue(), "Building static executable images is currently only supported on Linux. Remove the '--static' option or build on a Linux machine");
throw UserError.invalidOptionValue(key, key.getValue(),
"Building static executable images is currently only supported on Linux. Remove the '--static' option or build on a Linux machine");
}
if (!LibCBase.targetLibCIs(MuslLibC.class)) {
throw UserError.invalidOptionValue(key, key.getValue(),
@@ -1383,7 +1388,7 @@ public static class TruffleStableOptions {

@Option(help = "Enable fallback to mremap for initializing the image heap.")//
public static final HostedOptionKey<Boolean> MremapImageHeap = new HostedOptionKey<>(true, key -> {
if (!Platform.includedIn(Platform.LINUX.class)) {
if (key.hasBeenSet() && !Platform.includedIn(Platform.LINUX.class)) {
throw UserError.invalidOptionValue(key, key.getValue(), "Mapping the image heap with mremap() is only supported on Linux.");
}
});
Original file line number Diff line number Diff line change
@@ -22,36 +22,36 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.core.option;

import org.graalvm.collections.UnmodifiableMapCursor;
package com.oracle.svm.hosted.option;

import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.option.SubstrateOptionKey;
import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl;

import jdk.graal.compiler.options.OptionKey;
import jdk.graal.compiler.options.OptionDescriptor;

@AutomaticallyRegisteredFeature
public class ValidateImageBuildOptionsFeature implements InternalFeature {
public class BuildTimeOptionValidationFeature implements InternalFeature {
@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
UnmodifiableMapCursor<OptionKey<?>, Object> cursor = RuntimeOptionValues.singleton().getMap().getEntries();
BeforeAnalysisAccessImpl a = (BeforeAnalysisAccessImpl) access;
HostedOptionParser optionParser = a.getImageClassLoader().classLoaderSupport.getHostedOptionParser();

var cursor = optionParser.getAllHostedOptions().getEntries();
while (cursor.advance()) {
validate(cursor.getKey());
validate(cursor.getValue());
}

cursor = HostedOptionValues.singleton().getMap().getEntries();
cursor = optionParser.getAllRuntimeOptions().getEntries();
while (cursor.advance()) {
validate(cursor.getKey());
validate(cursor.getValue());
}
}

private static void validate(OptionKey<?> option) {
if (option instanceof SubstrateOptionKey) {
SubstrateOptionKey<?> o = (SubstrateOptionKey<?>) option;
if (o.hasBeenSet()) {
o.validate();
}
private static void validate(OptionDescriptor desc) {
if (desc.getOptionKey() instanceof SubstrateOptionKey<?> option) {
option.validate();
}
}
}
Original file line number Diff line number Diff line change
@@ -133,6 +133,14 @@ public List<String> getArguments() {
return arguments;
}

public EconomicMap<String, OptionDescriptor> getAllHostedOptions() {
return allHostedOptions;
}

public EconomicMap<String, OptionDescriptor> getAllRuntimeOptions() {
return allRuntimeOptions;
}

@Override
public EconomicMap<OptionKey<?>, Object> getHostedValues() {
return hostedValues;