Skip to content

Commit 1d8e313

Browse files
author
Christian Wimmer
committed
[GR-51002] Improve intrinsification of method handles.
PullRequest: graal/16410
2 parents 3f1f622 + 6e0c051 commit 1d8e313

File tree

17 files changed

+365
-309
lines changed

17 files changed

+365
-309
lines changed

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/PEGraphDecoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ public String getNodeClassName() {
344344
}
345345

346346
protected class PENonAppendGraphBuilderContext extends CoreProvidersDelegate implements GraphBuilderContext {
347-
protected final PEMethodScope methodScope;
347+
public final PEMethodScope methodScope;
348348
protected final Invoke invoke;
349349

350350
@Override

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This changelog summarizes major changes to GraalVM Native Image.
1818
* (GR-30433) Disallow the deprecated environment variable USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM=false.
1919
* (GR-49655) Experimental support for parts of the [Foreign Function & Memory API](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ForeignInterface.md) (part of "Project Panama", [JEP 454](https://openjdk.org/jeps/454)) on AMD64. Must be enabled with `-H:+ForeignAPISupport` (requiring `-H:+UnlockExperimentalVMOptions`).
2020
* (GR-46407) Correctly rethrow build-time linkage errors at run-time for registered reflection queries.
21+
* (GR-51002) Improve intrinsification of method handles. This especially improves the performance of `equals` and `hashCode` methods for records, which use method handles that are now intrinsified.
2122

2223
## GraalVM for JDK 21 (Internal Version 23.1.0)
2324
* (GR-35746) Lower the default aligned chunk size from 1 MB to 512 KB for the serial and epsilon GCs, reducing memory usage and image size in many cases.

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ protected void scanEmbeddedRoot(JavaConstant root, Object position) {
145145
try {
146146
scanningObserver.forEmbeddedRoot(root, reason);
147147
scanConstant(root, reason);
148-
} catch (UnsupportedFeatureException ex) {
148+
} catch (UnsupportedFeatureException | AnalysisError.TypeNotFoundError ex) {
149149
bb.getUnsupportedFeatures().addMessage(reason.toString(), reason.getMethod(), ex.getMessage(), null, ex);
150150
}
151151
}
@@ -205,7 +205,7 @@ protected void scanField(AnalysisField field, JavaConstant receiver, ScanReason
205205
scanningObserver.forPrimitiveFieldValue(receiver, field, fieldValue, reason);
206206
}
207207

208-
} catch (UnsupportedFeatureException ex) {
208+
} catch (UnsupportedFeatureException | AnalysisError.TypeNotFoundError ex) {
209209
unsupportedFeatureDuringFieldScan(bb, field, receiver, ex, reason);
210210
}
211211
}
@@ -268,7 +268,7 @@ protected final void scanArray(JavaConstant array, ScanReason prevReason) {
268268
try {
269269
JavaConstant element = bb.getUniverse().getSnippetReflection().forObject(bb.getUniverse().replaceObject(e));
270270
scanArrayElement(array, arrayType, reason, idx, element);
271-
} catch (UnsupportedFeatureException ex) { /* Object replacement can throw. */
271+
} catch (UnsupportedFeatureException | AnalysisError.TypeNotFoundError ex) {
272272
unsupportedFeatureDuringConstantScan(bb, bb.getUniverse().getSnippetReflection().forObject(e), ex, reason);
273273
}
274274
}
@@ -313,7 +313,7 @@ public void scanConstant(JavaConstant value, ScanReason reason) {
313313
* Use the constant hashCode as a key for the unsupported feature to register only one error
314314
* message if the constant is reachable from multiple places.
315315
*/
316-
public static void unsupportedFeatureDuringConstantScan(BigBang bb, JavaConstant constant, UnsupportedFeatureException e, ScanReason reason) {
316+
public static void unsupportedFeatureDuringConstantScan(BigBang bb, JavaConstant constant, Throwable e, ScanReason reason) {
317317
unsupportedFeature(bb, String.valueOf(receiverHashCode(constant)), e.getMessage(), reason);
318318
}
319319

@@ -322,11 +322,11 @@ public static void unsupportedFeatureDuringConstantScan(BigBang bb, JavaConstant
322322
* only one error message if the value is reachable from multiple places. For example both the
323323
* heap scanning and the heap verification would scan a field that contains an illegal value.
324324
*/
325-
public static void unsupportedFeatureDuringFieldScan(BigBang bb, AnalysisField field, JavaConstant receiver, UnsupportedFeatureException e, ScanReason reason) {
325+
public static void unsupportedFeatureDuringFieldScan(BigBang bb, AnalysisField field, JavaConstant receiver, Throwable e, ScanReason reason) {
326326
unsupportedFeature(bb, (receiver != null ? receiverHashCode(receiver) + "_" : "") + field.format("%H.%n"), e.getMessage(), reason);
327327
}
328328

329-
public static void unsupportedFeatureDuringFieldFolding(BigBang bb, AnalysisField field, JavaConstant receiver, UnsupportedFeatureException e, AnalysisMethod parsedMethod, int bci) {
329+
public static void unsupportedFeatureDuringFieldFolding(BigBang bb, AnalysisField field, JavaConstant receiver, Throwable e, AnalysisMethod parsedMethod, int bci) {
330330
ScanReason reason = new FieldConstantFold(field, parsedMethod, bci, receiver, new MethodParsing(parsedMethod));
331331
unsupportedFeature(bb, (receiver != null ? receiverHashCode(receiver) + "_" : "") + field.format("%H.%n"), e.getMessage(), reason);
332332
}
@@ -448,7 +448,7 @@ private void doScan(WorklistEntry entry) {
448448
/* Scan the array elements. */
449449
scanArray(entry.constant, entry.reason);
450450
}
451-
} catch (UnsupportedFeatureException ex) {
451+
} catch (UnsupportedFeatureException | AnalysisError.TypeNotFoundError ex) {
452452
unsupportedFeatureDuringConstantScan(bb, entry.constant, ex, entry.reason);
453453
}
454454
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,9 @@ public void rescanField(Object receiver, Field reflectionField, ScanReason reaso
595595
if (type.isReachable()) {
596596
AnalysisField field = metaAccess.lookupJavaField(reflectionField);
597597
assert !field.isStatic() : field;
598+
if (!field.isReachable()) {
599+
return;
600+
}
598601
JavaConstant receiverConstant = asConstant(receiver);
599602
Optional<JavaConstant> replaced = maybeReplace(receiverConstant, reason);
600603
if (replaced.isPresent()) {

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.oracle.graal.pointsto.meta.AnalysisField;
3939
import com.oracle.graal.pointsto.meta.AnalysisMethod;
4040
import com.oracle.graal.pointsto.meta.HostedProviders;
41+
import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisPolicy.AbstractPolicyScope;
4142
import com.oracle.graal.pointsto.util.AnalysisError;
4243
import com.oracle.graal.pointsto.util.GraalAccess;
4344
import com.oracle.svm.util.ReflectionUtil;
@@ -60,6 +61,7 @@
6061
import jdk.graal.compiler.nodes.ValueNode;
6162
import jdk.graal.compiler.nodes.calc.IsNullNode;
6263
import jdk.graal.compiler.nodes.extended.UnsafeAccessNode;
64+
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext;
6365
import jdk.graal.compiler.nodes.graphbuilderconf.InlineInvokePlugin;
6466
import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugin;
6567
import jdk.graal.compiler.nodes.graphbuilderconf.LoopExplosionPlugin;
@@ -78,7 +80,7 @@ public class InlineBeforeAnalysisGraphDecoder extends PEGraphDecoder {
7880

7981
public class InlineBeforeAnalysisMethodScope extends PEMethodScope {
8082

81-
public final InlineBeforeAnalysisPolicy.AbstractPolicyScope policyScope;
83+
public final AbstractPolicyScope policyScope;
8284

8385
private boolean inliningAborted;
8486

@@ -103,7 +105,7 @@ public class InlineBeforeAnalysisMethodScope extends PEMethodScope {
103105
for (int i = 0; i < arguments.length; i++) {
104106
constArgsWithReceiver[i] = arguments[i].isConstant();
105107
}
106-
policyScope = policy.openCalleeScope(cast(caller).policyScope, bb.getMetaAccess(), method, constArgsWithReceiver, invokeData.intrinsifiedMethodHandle);
108+
policyScope = policy.openCalleeScope(cast(caller).policyScope, method);
107109
if (graph.getDebug().isLogEnabled()) {
108110
graph.getDebug().logv(" ".repeat(inliningDepth) + "openCalleeScope for " + method.format("%H.%n(%p)") + ": " + policyScope);
109111
}
@@ -121,6 +123,27 @@ static void recordInlined(InlineBeforeAnalysisMethodScope callerScope, InlineBef
121123
}
122124
}
123125

126+
static final class InlineBeforeAnalysisInlineInvokePlugin implements InlineInvokePlugin {
127+
128+
private final InlineBeforeAnalysisPolicy policy;
129+
130+
InlineBeforeAnalysisInlineInvokePlugin(InlineBeforeAnalysisPolicy policy) {
131+
this.policy = policy;
132+
}
133+
134+
@Override
135+
public InlineInfo shouldInlineInvoke(GraphBuilderContext b, ResolvedJavaMethod m, ValueNode[] args) {
136+
AnalysisMethod method = (AnalysisMethod) m;
137+
138+
AbstractPolicyScope policyScope = cast(((PENonAppendGraphBuilderContext) b).methodScope).policyScope;
139+
if (policy.shouldInlineInvoke(b, policyScope, method, args)) {
140+
return policy.createInvokeInfo(method);
141+
} else {
142+
return InlineInfo.DO_NOT_INLINE_WITH_EXCEPTION;
143+
}
144+
}
145+
}
146+
124147
private Field dmhStaticAccessorOffsetField;
125148
private Field dmhStaticAccessorBaseField;
126149
private AnalysisField dmhStaticAccessorOffsetAnalysisField;
@@ -301,8 +324,19 @@ private void ensureDMHStaticAccessorFieldsInitialized() {
301324
}
302325

303326
@Override
304-
protected void handleNonInlinedInvoke(MethodScope methodScope, LoopScope loopScope, InvokeData invokeData) {
327+
protected void handleNonInlinedInvoke(MethodScope ms, LoopScope loopScope, InvokeData invokeData) {
328+
InlineBeforeAnalysisMethodScope methodScope = cast(ms);
305329
maybeAbortInlining(methodScope, loopScope, invokeData.invoke.asNode());
330+
331+
if (!methodScope.inliningAborted && methodScope.isInlinedMethod()) {
332+
if (graph.getDebug().isLogEnabled()) {
333+
graph.getDebug().logv(" ".repeat(methodScope.inliningDepth) + " nonInlinedInvoke " + invokeData.callTarget.targetMethod() + ": " + methodScope.policyScope);
334+
}
335+
if (!methodScope.policyScope.processNonInlinedInvoke(providers, invokeData.callTarget)) {
336+
abortInlining(methodScope);
337+
}
338+
}
339+
306340
super.handleNonInlinedInvoke(methodScope, loopScope, invokeData);
307341
}
308342

@@ -494,7 +528,7 @@ private void killControlFlowNodes(PEMethodScope inlineScope, FixedNode start) {
494528
* at the cost of this ugly cast.
495529
*/
496530
@SuppressWarnings("unchecked")
497-
protected InlineBeforeAnalysisMethodScope cast(MethodScope methodScope) {
531+
protected static InlineBeforeAnalysisMethodScope cast(MethodScope methodScope) {
498532
return (InlineBeforeAnalysisMethodScope) methodScope;
499533
}
500534

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisInlineInvokePlugin.java

Lines changed: 0 additions & 51 deletions
This file was deleted.

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@
3030

3131
import jdk.graal.compiler.graph.Node;
3232
import jdk.graal.compiler.graph.NodeSourcePosition;
33+
import jdk.graal.compiler.nodes.CallTargetNode;
3334
import jdk.graal.compiler.nodes.FixedWithNextNode;
3435
import jdk.graal.compiler.nodes.ValueNode;
3536
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext;
3637
import jdk.graal.compiler.nodes.graphbuilderconf.InlineInvokePlugin.InlineInfo;
3738
import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin;
39+
import jdk.graal.compiler.nodes.spi.CoreProviders;
3840

3941
/**
4042
* Provides the policy which methods are inlined by {@link InlineBeforeAnalysis}. If
@@ -77,6 +79,8 @@ protected AbstractPolicyScope(int inliningDepth) {
7779
* decision on the current list of usages. The list of usages is often but not always empty.
7880
*/
7981
public abstract boolean processNode(AnalysisMetaAccess metaAccess, AnalysisMethod method, Node node);
82+
83+
public abstract boolean processNonInlinedInvoke(CoreProviders providers, CallTargetNode node);
8084
}
8185

8286
protected final NodePlugin[] nodePlugins;
@@ -85,7 +89,7 @@ protected InlineBeforeAnalysisPolicy(NodePlugin[] nodePlugins) {
8589
this.nodePlugins = nodePlugins;
8690
}
8791

88-
protected abstract boolean shouldInlineInvoke(GraphBuilderContext b, AnalysisMethod method, ValueNode[] args);
92+
protected abstract boolean shouldInlineInvoke(GraphBuilderContext b, AbstractPolicyScope policyScope, AnalysisMethod method, ValueNode[] args);
8993

9094
protected abstract InlineInfo createInvokeInfo(AnalysisMethod method);
9195

@@ -97,8 +101,7 @@ protected InlineBeforeAnalysisPolicy(NodePlugin[] nodePlugins) {
97101

98102
protected abstract AbstractPolicyScope createRootScope();
99103

100-
protected abstract AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess,
101-
AnalysisMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle);
104+
protected abstract AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMethod method);
102105

103106
/** @see InlineBeforeAnalysisGraphDecoder#shouldOmitIntermediateMethodInStates */
104107
protected boolean shouldOmitIntermediateMethodInState(AnalysisMethod method) {
@@ -108,7 +111,7 @@ protected boolean shouldOmitIntermediateMethodInState(AnalysisMethod method) {
108111
public static final InlineBeforeAnalysisPolicy NO_INLINING = new InlineBeforeAnalysisPolicy(new NodePlugin[0]) {
109112

110113
@Override
111-
protected boolean shouldInlineInvoke(GraphBuilderContext b, AnalysisMethod method, ValueNode[] args) {
114+
protected boolean shouldInlineInvoke(GraphBuilderContext b, AbstractPolicyScope policyScope, AnalysisMethod method, ValueNode[] args) {
112115
return false;
113116
}
114117

@@ -143,8 +146,7 @@ protected AbstractPolicyScope createRootScope() {
143146
}
144147

145148
@Override
146-
protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess,
147-
AnalysisMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle) {
149+
protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMethod method) {
148150
throw AnalysisError.shouldNotReachHere("NO_INLINING policy should not try to inline");
149151
}
150152
};

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.lang.invoke.MethodHandle;
2828
import java.lang.invoke.VarHandle;
2929
import java.lang.reflect.Field;
30+
import java.lang.reflect.Method;
3031
import java.lang.reflect.Modifier;
3132
import java.util.HashMap;
3233
import java.util.Map;
@@ -99,6 +100,70 @@ public class VarHandleFeature implements InternalFeature {
99100
private final ConcurrentMap<Object, Boolean> processedVarHandles = new ConcurrentHashMap<>();
100101
private Consumer<Field> markAsUnsafeAccessed;
101102

103+
@Override
104+
public void duringSetup(DuringSetupAccess access) {
105+
/*
106+
* Initialize fields of VarHandle instances that are @Stable eagerly, so that during method
107+
* handle intrinsification loads of those fields and array elements can be constant-folded.
108+
*
109+
* Note that we do this on purpose here in an object replacer, and not in an object
110+
* reachability handler: Intrinsification happens as part of method inlining before
111+
* analysis, i.e., before the static analysis, i.e., before the VarHandle object itself is
112+
* marked as reachable. The goal of intrinsification is to actually avoid making the
113+
* VarHandle object itself reachable.
114+
*/
115+
access.registerObjectReplacer(VarHandleFeature::eagerlyInitializeVarHandle);
116+
}
117+
118+
private static Object eagerlyInitializeVarHandle(Object obj) {
119+
if (obj instanceof VarHandle varHandle) {
120+
eagerlyInitializeVarHandle(varHandle);
121+
}
122+
return obj;
123+
}
124+
125+
private static final Field varHandleVFormField = ReflectionUtil.lookupField(VarHandle.class, "vform");
126+
private static final Method varFormInitMethod = ReflectionUtil.lookupMethod(ReflectionUtil.lookupClass(false, "java.lang.invoke.VarForm"), "getMethodType_V", int.class);
127+
private static final Method varHandleGetMethodHandleMethod = ReflectionUtil.lookupMethod(VarHandle.class, "getMethodHandle", int.class);
128+
129+
public static void eagerlyInitializeVarHandle(VarHandle varHandle) {
130+
try {
131+
/*
132+
* The field VarHandle.vform.methodType_V_table is a @Stable field but initialized
133+
* lazily on first access. Therefore, constant folding can happen only after
134+
* initialization has happened. We force initialization by invoking the method
135+
* VarHandle.vform.getMethodType_V(0).
136+
*/
137+
Object varForm = varHandleVFormField.get(varHandle);
138+
varFormInitMethod.invoke(varForm, 0);
139+
140+
/*
141+
* The AccessMode used for the access that we are going to intrinsify is hidden in a
142+
* AccessDescriptor object that is also passed in as a parameter to the intrinsified
143+
* method. Initializing all AccessMode enum values is easier than trying to extract the
144+
* actual AccessMode.
145+
*/
146+
for (VarHandle.AccessMode accessMode : VarHandle.AccessMode.values()) {
147+
/*
148+
* Force initialization of the @Stable field VarHandle.vform.memberName_table.
149+
*/
150+
boolean isAccessModeSupported = varHandle.isAccessModeSupported(accessMode);
151+
/*
152+
* Force initialization of the @Stable field
153+
* VarHandle.typesAndInvokers.methodType_table.
154+
*/
155+
varHandle.accessModeType(accessMode);
156+
157+
if (isAccessModeSupported) {
158+
/* Force initialization of the @Stable field VarHandle.methodHandleTable. */
159+
varHandleGetMethodHandleMethod.invoke(varHandle, accessMode.ordinal());
160+
}
161+
}
162+
} catch (ReflectiveOperationException ex) {
163+
throw VMError.shouldNotReachHere(ex);
164+
}
165+
}
166+
102167
@Override
103168
public void afterRegistration(AfterRegistrationAccess access) {
104169
try {

0 commit comments

Comments
 (0)