Skip to content

Commit 54d69dc

Browse files
committed
[GR-65625] [GR-66764] Refine layer compatibility checking for build options.
PullRequest: graal/21257
2 parents 6273c16 + 293ff12 commit 54d69dc

File tree

6 files changed

+112
-67
lines changed

6 files changed

+112
-67
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,6 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean o
633633
@Option(help = "Enable detection and runtime container configuration support.")//
634634
public static final HostedOptionKey<Boolean> UseContainerSupport = new HostedOptionKey<>(true);
635635

636-
@LayerVerifiedOption(kind = Kind.Changed, severity = Severity.Error)//
637636
@Option(help = "The size of each thread stack at run-time, in bytes.", type = OptionType.User)//
638637
public static final RuntimeOptionKey<Long> StackSize = new RuntimeOptionKey<>(0L);
639638

@@ -876,8 +875,6 @@ private static void validateZapNativeMemory(HostedOptionKey<Boolean> optionKey)
876875
/*
877876
* Isolate tear down options.
878877
*/
879-
880-
@LayerVerifiedOption(kind = Kind.Changed, severity = Severity.Error)//
881878
@Option(help = "The number of seconds before and between which tearing down an isolate gives a warning message. 0 implies no warning.")//
882879
public static final RuntimeOptionKey<Long> TearDownWarningSeconds = new RuntimeOptionKey<>(0L, RelevantForCompilationIsolates);
883880

@@ -914,7 +911,7 @@ public static long getTearDownFailureNanos() {
914911
@Option(help = "Perform trivial method inlining in the AOT compiled native image")//
915912
public static final HostedOptionKey<Boolean> AOTTrivialInline = new HostedOptionKey<>(true);
916913

917-
@LayerVerifiedOption(kind = Kind.Removed, severity = Severity.Error, positional = false)//
914+
@LayerVerifiedOption(kind = Kind.Removed, severity = Severity.Warn, positional = false)//
918915
@Option(help = "file:doc-files/NeverInlineHelp.txt", type = OptionType.Debug)//
919916
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> NeverInline = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());
920917

@@ -1074,8 +1071,13 @@ public static int codeAlignment(OptionValues options) {
10741071
@Option(help = "Determines if debugging-specific helper methods are embedded into the image. Those methods can be called directly from the debugger to obtain or print additional information.", type = OptionType.Debug) //
10751072
public static final HostedOptionKey<Boolean> IncludeDebugHelperMethods = new HostedOptionKey<>(false);
10761073

1074+
private static final String ENABLE_DEBUGINFO_OPTION = "-g";
1075+
// Only raise error if -g is used in current layer build but missing in the previous layer build
1076+
@LayerVerifiedOption(apiOption = ENABLE_DEBUGINFO_OPTION, kind = Kind.Added, severity = Severity.Error, message = "If you want to use " + ENABLE_DEBUGINFO_OPTION +
1077+
" in this layer, use a base layer that also got built with " + ENABLE_DEBUGINFO_OPTION + ".")//
1078+
// ... but use stricter check for raw (non-API) use of GenerateDebugInfo
10771079
@LayerVerifiedOption(kind = Kind.Changed, severity = Severity.Error)//
1078-
@APIOption(name = "-g", fixedValue = "2", customHelp = "generate debugging information")//
1080+
@APIOption(name = ENABLE_DEBUGINFO_OPTION, fixedValue = "2", customHelp = "generate debugging information")//
10791081
@Option(help = "Insert debug info into the generated native image or library")//
10801082
public static final HostedOptionKey<Integer> GenerateDebugInfo = new HostedOptionKey<>(0, SubstrateOptions::validateGenerateDebugInfo) {
10811083
@Override
@@ -1226,7 +1228,6 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Integer o
12261228
@Option(help = "The largest page size of machines that can run the image. The default of 0 automatically selects a typically suitable value.")//
12271229
protected static final HostedOptionKey<Integer> PageSize = new HostedOptionKey<>(0);
12281230

1229-
@LayerVerifiedOption(kind = Kind.Changed, severity = Severity.Error)//
12301231
@Option(help = "Physical memory size (in bytes). By default, the value is queried from the OS/container during VM startup.", type = OptionType.Expert)//
12311232
public static final RuntimeOptionKey<Long> MaxRAM = new RuntimeOptionKey<>(0L, RegisterForIsolateArgumentParser);
12321233

@@ -1259,7 +1260,6 @@ protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Integer o
12591260
@Option(help = "For internal purposes only. Disables type id result verification even when running with assertions enabled.", stability = OptionStability.EXPERIMENTAL, type = OptionType.Debug)//
12601261
public static final HostedOptionKey<Boolean> DisableTypeIdResultVerification = new HostedOptionKey<>(true);
12611262

1262-
@LayerVerifiedOption(kind = Kind.Changed, severity = Severity.Error)//
12631263
@Option(help = "Enables signal handling", stability = OptionStability.EXPERIMENTAL, type = Expert)//
12641264
public static final RuntimeOptionKey<Boolean> EnableSignalHandling = new RuntimeOptionKey<>(null, Immutable) {
12651265
@Override

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LayerVerifiedOption.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,14 @@ enum Kind {
9797
* specify it somewhere in its sequence of options.
9898
*/
9999
boolean positional() default true;
100+
101+
/**
102+
* If the {@code HostedOption} field (this annotation is used with) also has {@link APIOption}
103+
* annotations, this annotation element can be used to bind this annotation to a specific
104+
* {@link APIOption} annotation instead of being valid for all kinds of {@code HostedOption}
105+
* use. Note that one can also have additional {@code @LayerVerifiedOption} annotations that do
106+
* not make use of {@code apiOption} on the same {@code HostedOption} field to specify
107+
* compatibility checking that should apply for raw (non-API) use of the option.
108+
*/
109+
String apiOption() default "";
100110
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.nio.MappedByteBuffer;
2929
import java.nio.channels.FileChannel;
3030
import java.nio.file.Path;
31+
import java.util.ArrayList;
3132
import java.util.HashMap;
3233
import java.util.List;
3334
import java.util.Map;
@@ -43,10 +44,10 @@
4344
import com.oracle.svm.core.option.LayerVerifiedOption;
4445
import com.oracle.svm.core.option.LocatableMultiOptionValue.ValueWithOrigin;
4546
import com.oracle.svm.core.option.OptionUtils;
47+
import com.oracle.svm.core.option.RuntimeOptionKey;
4648
import com.oracle.svm.core.option.SubstrateOptionsParser;
4749
import com.oracle.svm.core.util.ArchiveSupport;
4850
import com.oracle.svm.core.util.UserError;
49-
import com.oracle.svm.core.util.VMError;
5051
import com.oracle.svm.hosted.ImageClassLoader;
5152
import com.oracle.svm.hosted.NativeImageClassLoaderSupport;
5253
import com.oracle.svm.hosted.c.NativeLibraries;
@@ -312,9 +313,10 @@ public static HostedImageLayerBuildingSupport initialize(HostedOptionValues valu
312313
return imageLayerBuildingSupport;
313314
}
314315

315-
record OptionLayerVerificationRequests(OptionDescriptor option, EconomicMap<LayerVerifiedOption.Kind, LayerVerifiedOption> requests) {
316+
record OptionLayerVerificationRequests(OptionDescriptor option, List<LayerVerifiedOption> requests) {
316317
OptionLayerVerificationRequests(OptionDescriptor option) {
317-
this(option, EconomicMap.create());
318+
this(option, new ArrayList<>());
319+
assert !(option.getOptionKey() instanceof RuntimeOptionKey) : "LayerVerifiedOption annotation on NI runtime-option";
318320
}
319321
}
320322

@@ -326,23 +328,17 @@ public static Map<String, OptionLayerVerificationRequests> collectLayerVerificat
326328
Map<String, OptionLayerVerificationRequests> result = new HashMap<>();
327329
for (OptionDescriptor optionDescriptor : hostedOptions.getValues()) {
328330
for (LayerVerifiedOption layerVerification : OptionUtils.getAnnotationsByType(optionDescriptor, LayerVerifiedOption.class)) {
329-
result.computeIfAbsent(optionDescriptor.getName(), key -> new OptionLayerVerificationRequests(optionDescriptor)).requests.put(layerVerification.kind(), layerVerification);
331+
result.computeIfAbsent(optionDescriptor.getName(), key -> new OptionLayerVerificationRequests(optionDescriptor)).requests.add(layerVerification);
330332
}
331333
}
332334
return result;
333335
}
334336

335337
@SuppressFBWarnings(value = "NP", justification = "FB reports null pointer dereferencing because it doesn't see through UserError.guarantee.")
336338
public static void setupSharedLayerLibrary(NativeLibraries nativeLibs) {
337-
Path sharedLibPath = HostedImageLayerBuildingSupport.singleton().getLoadLayerArchiveSupport().getSharedLibraryPath();
338-
Path parent = sharedLibPath.getParent();
339-
VMError.guarantee(parent != null, "Shared layer library path doesn't have a parent.");
340-
nativeLibs.getLibraryPaths().add(parent.toString());
341-
Path fileName = sharedLibPath.getFileName();
342-
VMError.guarantee(fileName != null, "Cannot determine shared layer library file name.");
343-
String fullLibName = fileName.toString();
344-
VMError.guarantee(fullLibName.startsWith("lib") && fullLibName.endsWith(".so"), "Expecting that shared layer library file starts with lib and ends with .so. Found: %s", fullLibName);
345-
String libName = fullLibName.substring("lib".length(), fullLibName.length() - ".so".length());
339+
LoadLayerArchiveSupport archiveSupport = HostedImageLayerBuildingSupport.singleton().getLoadLayerArchiveSupport();
340+
nativeLibs.getLibraryPaths().add(archiveSupport.getSharedLibraryPath().toString());
341+
String libName = archiveSupport.getSharedLibraryBaseName();
346342
HostedDynamicLayerInfo.singleton().registerLibName(libName);
347343
nativeLibs.addDynamicNonJniLibrary(libName);
348344
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class LayerArchiveSupport {
5353
private static final String SNAPSHOT_GRAPHS_FILE_NAME = "layer-snapshot-graphs.big";
5454
private static final String LAYER_INFO_MESSAGE_PREFIX = "Native Image Layers";
5555
protected static final String LAYER_TEMP_DIR_PREFIX = "layerRoot_";
56+
protected static final String SHARED_LIB_NAME_PREFIX = "lib";
5657

5758
public static final String LAYER_FILE_EXTENSION = ".nil";
5859

@@ -103,7 +104,11 @@ public Path getSnapshotGraphsPath() {
103104
}
104105

105106
public Path getSharedLibraryPath() {
106-
return layerDir.resolve(layerProperties.layerName() + ".so");
107+
return layerDir;
108+
}
109+
110+
public String getSharedLibraryBaseName() {
111+
return layerProperties.layerName().substring(SHARED_LIB_NAME_PREFIX.length());
107112
}
108113

109114
private static final Path layerPropertiesFileName = Path.of("META-INF/nilayer.properties");

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727
import java.io.IOException;
2828
import java.nio.file.Files;
2929
import java.nio.file.Path;
30+
import java.util.ArrayList;
3031
import java.util.HashMap;
3132
import java.util.List;
3233
import java.util.Map;
3334
import java.util.Set;
3435
import java.util.function.Function;
36+
import java.util.stream.Collectors;
3537
import java.util.stream.Stream;
3638

3739
import com.oracle.svm.core.option.LayerVerifiedOption;
@@ -105,59 +107,84 @@ private static boolean verifyCompatibility(List<String> previousArgs, List<Strin
105107
List<DiffResult<String>> diffResults = DiffTool.diffResults(filteredLeft, filteredRight);
106108
Map<DiffResult<String>, Severity> violations = new HashMap<>();
107109
for (var diffResult : diffResults) {
108-
Set<Kind> verificationKinds = switch (diffResult.kind()) {
110+
DiffResult.Kind diffResultKind = diffResult.kind();
111+
Set<Kind> verificationKinds = switch (diffResultKind) {
112+
case Equal -> Set.of();
109113
case Removed -> Set.of(Kind.Removed, Kind.Changed);
110114
case Added -> Set.of(Kind.Added, Kind.Changed);
111-
default -> Set.of();
112115
};
113-
for (Kind verificationKind : verificationKinds) {
114-
ArgumentOrigin argumentOrigin = splitArgumentOrigin(diffResult.getEntry(left, right));
115-
NameValue argumentNameAndValue = argumentOrigin.nameValue();
116-
var perOptionVerifications = allRequests.get(argumentNameAndValue.name);
117-
if (perOptionVerifications == null) {
118-
continue;
119-
}
120-
LayerVerifiedOption request = perOptionVerifications.requests().get(verificationKind);
121-
if (request == null || request.positional() != positional) {
122-
continue;
123-
}
116+
if (verificationKinds.isEmpty()) {
117+
continue;
118+
}
124119

125-
OptionOrigin origin = OptionOrigin.from(argumentOrigin.origin);
126-
String argument = SubstrateOptionsParser.commandArgument(perOptionVerifications.option().getOptionKey(), argumentNameAndValue.value);
127-
String message = switch (diffResult.kind()) {
128-
case Removed -> "Previous layer was";
129-
case Added -> "Current layer gets";
130-
case Equal -> throw VMError.shouldNotReachHere("diff for equal");
131-
} + " built with option argument '" + argument + "' from " + origin + ".";
132-
String suffix;
133-
if (!request.message().isEmpty()) {
134-
suffix = request.message();
135-
} else {
136-
/* fallback to generic verification message */
137-
suffix = "This is also required to be specified for the " + switch (diffResult.kind()) {
138-
case Removed -> "current layered image build";
139-
case Added -> "previous layer build";
140-
case Equal -> throw VMError.shouldNotReachHere("diff for equal");
141-
};
120+
ArgumentOrigin argumentOrigin = splitArgumentOrigin(diffResult.getEntry(left, right));
121+
NameValue argumentNameAndValue = argumentOrigin.nameValue();
122+
var perOptionVerifications = allRequests.get(argumentNameAndValue.name);
123+
if (perOptionVerifications == null) {
124+
continue;
125+
}
126+
127+
List<LayerVerifiedOption> requests = perOptionVerifications.requests().stream()
128+
.filter(request -> request.positional() == positional)
129+
.collect(Collectors.toList());
130+
String argument = SubstrateOptionsParser.commandArgument(perOptionVerifications.option().getOptionKey(), argumentNameAndValue.value);
131+
List<LayerVerifiedOption> matchingAPIRequest = new ArrayList<>();
132+
requests.removeIf(request -> {
133+
if (request.apiOption().isEmpty()) {
134+
// Keep all non-API requests
135+
return false;
142136
}
143-
message += " " + suffix + (positional ? " at the same position." : ".");
144-
Severity severity = request.severity();
145-
violations.put(diffResult, severity);
146-
if (verbose) {
147-
LogUtils.info("Error: ", message);
148-
} else {
149-
switch (severity) {
150-
case Warn -> LogUtils.warning(message);
151-
case Error -> {
152-
if (strict) {
153-
UserError.abort(message);
154-
} else {
155-
LogUtils.warning(message);
156-
}
157-
}
158-
}
137+
// Do record matching API requests ...
138+
if (request.apiOption().equals(argument)) {
139+
matchingAPIRequest.add(request);
159140
}
141+
// ... but remove all API request entries
142+
return true;
143+
});
144+
if (!matchingAPIRequest.isEmpty()) {
145+
/*
146+
* If we have a @LayerVerifiedOption annotation with a matching apiOption set, we
147+
* ignore other @LayerVerifiedOption annotations that do not have apiOption set.
148+
*/
149+
requests = matchingAPIRequest;
160150
}
151+
152+
requests.stream()
153+
.filter(request -> verificationKinds.contains(request.kind()))
154+
.forEach(request -> {
155+
156+
String message = switch (diffResultKind) {
157+
case Removed -> "Previous layer was";
158+
case Added -> "Current layer gets";
159+
case Equal -> throw VMError.shouldNotReachHere("diff for equal");
160+
} + " built with option argument '" + argument + "' from " + OptionOrigin.from(argumentOrigin.origin) + ".";
161+
if (!request.message().isEmpty()) {
162+
message += " " + request.message();
163+
} else {
164+
/* fallback to generic verification message */
165+
message += " This is also required to be specified for the " + switch (diffResultKind) {
166+
case Removed -> "current layered image build";
167+
case Added -> "previous layer build";
168+
case Equal -> throw VMError.shouldNotReachHere("diff for equal");
169+
} + (positional ? " at the same position." : ".");
170+
}
171+
Severity severity = request.severity();
172+
violations.put(diffResult, severity);
173+
if (verbose) {
174+
LogUtils.info("Error: ", message);
175+
} else {
176+
switch (severity) {
177+
case Warn -> LogUtils.warning(message);
178+
case Error -> {
179+
if (strict) {
180+
UserError.abort(message);
181+
} else {
182+
LogUtils.warning(message);
183+
}
184+
}
185+
}
186+
}
187+
});
161188
}
162189

163190
boolean violationsFound = !violations.isEmpty();

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
import com.oracle.svm.core.BuildArtifacts;
3333
import com.oracle.svm.core.BuildArtifacts.ArtifactType;
34+
import com.oracle.svm.core.SubstrateOptions;
35+
import com.oracle.svm.core.option.SubstrateOptionsParser;
3436
import com.oracle.svm.core.util.ArchiveSupport;
3537
import com.oracle.svm.core.util.UserError;
3638
import com.oracle.svm.hosted.NativeImageClassLoaderSupport;
@@ -41,6 +43,11 @@ public class WriteLayerArchiveSupport extends LayerArchiveSupport {
4143

4244
public WriteLayerArchiveSupport(String layerName, NativeImageClassLoaderSupport classLoaderSupport, Path tempDir, ArchiveSupport archiveSupport) {
4345
super(layerName, classLoaderSupport.getLayerFile(), tempDir.resolve(LAYER_TEMP_DIR_PREFIX + "write"), archiveSupport);
46+
if (!layerName.startsWith(SHARED_LIB_NAME_PREFIX)) {
47+
throw UserError.abort("Shared layer library image name given with '" +
48+
SubstrateOptionsParser.commandArgument(SubstrateOptions.Name, layerName) +
49+
"' needs to start with '" + SHARED_LIB_NAME_PREFIX + "'");
50+
}
4451
builderArguments.addAll(classLoaderSupport.getHostedOptionParser().getArguments());
4552
}
4653

0 commit comments

Comments
 (0)