-
Notifications
You must be signed in to change notification settings - Fork 38.1k
Commit
This commit leverages a subset of @philwebb initial experimentation to compute at build time the value of specific boolean static fields in native images. This enhancement is implemented for now as a GraalVM feature. The goal here is to keep an optimized footprint via build time code removal without leveraging build-time class initialization which is known for the blocking compatibility issues it introduces due to its viral nature. For now, the static fields initialized at build time with native are: - NativeDetector#imageCode - Fields with a name ending by "Present" in "org.springframework" package typically used for classpath check with ClassUtils#isPresent Closes gh-28624
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
description = "Spring Core GraalVM feature" | ||
|
||
configurations { | ||
classesOnlyElements { | ||
canBeConsumed = true | ||
canBeResolved = false | ||
} | ||
} | ||
|
||
artifacts { | ||
classesOnlyElements(compileJava.destinationDirectory) | ||
classesOnlyElements(sourceSets.main.resources.srcDirs) | ||
} | ||
|
||
tasks.withType(JavaCompile) { | ||
options.compilerArgs += [ | ||
"--add-modules", | ||
"jdk.internal.vm.ci", | ||
"--add-exports", | ||
"jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED" | ||
] | ||
} | ||
|
||
eclipse.classpath.file { | ||
whenMerged { | ||
entries.find{ it.path ==~ '.*JRE_CONTAINER.*' }.each { | ||
it.entryAttributes['module'] = true | ||
it.entryAttributes['add-exports'] = 'jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED' | ||
it.entryAttributes['limit-modules'] = 'java.se,jdk.accessibility,jdk.attach,jdk.compiler,jdk.httpserver,jdk.jartool,jdk.jconsole,jdk.jdi,jdk.management,jdk.sctp,jdk.security.auth,jdk.security.jgss,jdk.unsupported,jdk.dynalink,jdk.incubator.foreign,jdk.incubator.vector,jdk.javadoc,jdk.jfr,jdk.jshell,jdk.management.jfr,jdk.net,jdk.nio.mapmode,jdk.unsupported.desktop,jdk.jsobject,jdk.xml.dom,jdk.internal.vm.ci' | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
compileOnly("org.graalvm.nativeimage:svm") | ||
} | ||
|
||
tasks.withType(PublishToMavenRepository) { | ||
enabled = false | ||
} | ||
|
||
tasks.withType(PublishToMavenLocal) { | ||
enabled = false | ||
} | ||
|
||
tasks.withType(Javadoc) { | ||
enabled = false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* Copyright 2002-2022 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.aot.graalvm; | ||
|
||
import com.oracle.svm.core.annotate.AutomaticFeature; | ||
import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; | ||
import org.graalvm.compiler.debug.DebugContext; | ||
import org.graalvm.nativeimage.hosted.Feature; | ||
|
||
/** | ||
* GraalVM {@link Feature} that substitutes field values that match a certain pattern | ||
* with constants without causing build-time initialization. | ||
* | ||
* @author Phillip Webb | ||
* @author Sebastien Deleuze | ||
* @since 6.0 | ||
*/ | ||
@AutomaticFeature | ||
class ConstantFieldFeature implements Feature { | ||
|
||
@Override | ||
public void duringSetup(DuringSetupAccess access) { | ||
duringSetup((DuringSetupAccessImpl) access); | ||
} | ||
|
||
private void duringSetup(DuringSetupAccessImpl access) { | ||
DebugContext debug = access.getDebugContext(); | ||
try (DebugContext.Scope scope = debug.scope("ConstantFieldFeature.duringSetup")) { | ||
debug.log("Installing constant field substitution processor : " + scope); | ||
ClassLoader applicationClassLoader = access.getApplicationClassLoader(); | ||
ConstantFieldSubstitutionProcessor substitutionProcessor = | ||
new ConstantFieldSubstitutionProcessor(debug, applicationClassLoader); | ||
access.registerSubstitutionProcessor(substitutionProcessor); | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* Copyright 2002-2022 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.aot.graalvm; | ||
|
||
import java.lang.reflect.Field; | ||
import java.util.regex.Pattern; | ||
|
||
import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; | ||
import com.oracle.svm.core.meta.SubstrateObjectConstant; | ||
import com.oracle.svm.core.util.UserError; | ||
import jdk.vm.ci.meta.JavaConstant; | ||
import jdk.vm.ci.meta.JavaKind; | ||
import jdk.vm.ci.meta.ResolvedJavaField; | ||
import jdk.vm.ci.meta.ResolvedJavaType; | ||
import org.graalvm.compiler.debug.DebugContext; | ||
|
||
/** | ||
* {@link SubstitutionProcessor} to compute at build time the value of the | ||
* boolean static fields identified by {@link #patterns} in order to allow | ||
* efficient code shrinking without using class build time initialization. | ||
* | ||
* @author Phillip Webb | ||
* @author Sebastien Deleuze | ||
* @since 6.0 | ||
*/ | ||
class ConstantFieldSubstitutionProcessor extends SubstitutionProcessor { | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
sdeleuze
Author
Contributor
|
||
|
||
// Later should be an explicit signal, like an annotation or even a Java keyword | ||
private static Pattern[] patterns = { | ||
Pattern.compile(Pattern.quote("org.springframework.core.NativeDetector#imageCode")), | ||
Pattern.compile(Pattern.quote("org.springframework.") + ".*#.*Present"), | ||
}; | ||
|
||
private final ThrowawayClassLoader throwawayClassLoader; | ||
|
||
|
||
ConstantFieldSubstitutionProcessor(DebugContext debug, ClassLoader applicationClassLoader) { | ||
this.throwawayClassLoader = new ThrowawayClassLoader(applicationClassLoader); | ||
} | ||
|
||
|
||
@Override | ||
public ResolvedJavaField lookup(ResolvedJavaField field) { | ||
ResolvedJavaType declaringClass = field.getDeclaringClass(); | ||
if (field.getType().getJavaKind() == JavaKind.Boolean && field.isStatic()) { | ||
String fieldIdentifier = declaringClass.toJavaName() + "#" + field.getName(); | ||
for (Pattern pattern : patterns) { | ||
if (pattern.matcher(fieldIdentifier).matches()) { | ||
JavaConstant constant = lookupConstant(declaringClass.toJavaName(), field.getName()); | ||
if (constant != null) { | ||
// TODO Use proper logging only when --verbose is specified when https://github.com/oracle/graal/issues/4669 will be fixed | ||
System.out.println("Field " + fieldIdentifier + " set to " + constant.toValueString() + " at build time"); | ||
return new ConstantReadableJavaField(field, constant); | ||
} | ||
} | ||
} | ||
} | ||
return super.lookup(field); | ||
} | ||
|
||
private JavaConstant lookupConstant(String className, String fieldName) { | ||
try { | ||
Class<?> throwawayClass = this.throwawayClassLoader.loadClass(className); | ||
Field field = throwawayClass.getDeclaredField(fieldName); | ||
field.setAccessible(true); | ||
Object value = field.get(null); | ||
if (!(value instanceof Boolean)) { | ||
throw UserError.abort("Unable to get the value of " + className + "." + fieldName); | ||
} | ||
return SubstrateObjectConstant.forBoxedValue(JavaKind.Boolean, value); | ||
} | ||
catch (Exception ex) { | ||
throw new IllegalStateException("Unable to read value from " + className + "." + fieldName, ex); | ||
} | ||
} | ||
|
||
} |
An easier way to do the same thing should actually be to use a
NodePlugin
that constant-folds field accesses during bytecode parsing. See for example https://github.com/oracle/graal/blob/89e4cfc7aeea69970b60c64cd075ceb2a104e864/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/EarlyConstantFoldLoadFieldPlugin.java#L70But that of course is then still highly dependent on internals of the image generator, i.e., it will need to be fixed often when GraalVM changes.