Skip to content

Commit fcd3964

Browse files
committed
[GR-45250][GR-45734] Feature for logging build-time inferred method invocations otherwise requiring reachability registrations.
1 parent 171c45c commit fcd3964

File tree

4 files changed

+315
-0
lines changed

4 files changed

+315
-0
lines changed

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
import jdk.vm.ci.meta.JavaType;
8383
import jdk.vm.ci.meta.ResolvedJavaMethod;
8484

85+
import java.util.ArrayList;
86+
8587
/**
8688
* Used by a {@link GraphBuilderPlugin} to interface with an object that parses the bytecode of a
8789
* single {@linkplain #getMethod() method} as part of building a {@linkplain #getGraph() graph} .
@@ -281,6 +283,18 @@ default int getDepth() {
281283
return result;
282284
}
283285

286+
/**
287+
* Gets the inlined call stack for this context. A list with only one element implies that no
288+
* inlining has taken place.
289+
*/
290+
default List<StackTraceElement> getCallStack() {
291+
List<StackTraceElement> callStack = new ArrayList<>();
292+
for (GraphBuilderContext cur = this; cur != null; cur = cur.getParent()) {
293+
callStack.add(cur.getMethod().asStackTraceElement(cur.bci()));
294+
}
295+
return callStack;
296+
}
297+
284298
/**
285299
* Computes the recursive inlining depth of the provided method, i.e., counts how often the
286300
* provided method is already in the {@link #getParent()} chain starting at this context.

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676
import jdk.vm.ci.meta.ResolvedJavaType;
7777
import jdk.vm.ci.meta.Signature;
7878

79+
import java.util.ArrayList;
80+
import java.util.List;
81+
7982
/**
8083
* Implementation of {@link GraphBuilderContext} used to produce a graph for a method based on an
8184
* {@link InvocationPlugin} for the method.
@@ -325,6 +328,11 @@ public int getDepth() {
325328
return 0;
326329
}
327330

331+
@Override
332+
public List<StackTraceElement> getCallStack() {
333+
return new ArrayList<>();
334+
}
335+
328336
@Override
329337
public boolean parsingIntrinsic() {
330338
return false;

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,19 @@ public int getDepth() {
412412
return methodScope.inliningDepth;
413413
}
414414

415+
@Override
416+
public List<StackTraceElement> getCallStack() {
417+
List<StackTraceElement> callStack = new ArrayList<>(Arrays.asList(methodScope.getCallStack()));
418+
/*
419+
* If we're processing an invocation plugin, we want the top stack element to be the
420+
* callee of the method targeted by the plugin, and not the target itself.
421+
*/
422+
if (isParsingInvocationPlugin()) {
423+
callStack.removeFirst();
424+
}
425+
return callStack;
426+
}
427+
415428
@Override
416429
public int recursiveInliningDepth(ResolvedJavaMethod method) {
417430
int result = 0;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.strictconstantanalysis;
26+
27+
import com.oracle.svm.core.ParsingReason;
28+
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
29+
import com.oracle.svm.core.feature.InternalFeature;
30+
import com.oracle.svm.core.option.HostedOptionKey;
31+
import com.oracle.svm.core.util.VMError;
32+
import com.oracle.svm.hosted.ReachabilityRegistrationNode;
33+
import com.oracle.svm.util.LogUtils;
34+
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext;
35+
import jdk.graal.compiler.options.Option;
36+
import jdk.graal.compiler.util.json.JsonBuilder;
37+
import jdk.graal.compiler.util.json.JsonPrettyWriter;
38+
import jdk.graal.compiler.util.json.JsonWriter;
39+
import jdk.vm.ci.meta.ResolvedJavaMethod;
40+
import org.graalvm.collections.Pair;
41+
import org.graalvm.nativeimage.ImageSingletons;
42+
43+
import java.io.IOException;
44+
import java.nio.file.Path;
45+
import java.util.Arrays;
46+
import java.util.List;
47+
import java.util.Objects;
48+
import java.util.Optional;
49+
import java.util.Queue;
50+
import java.util.concurrent.ConcurrentLinkedQueue;
51+
import java.util.stream.Collectors;
52+
import java.util.stream.Stream;
53+
54+
@AutomaticallyRegisteredFeature
55+
public class InferredDynamicAccessLoggingFeature implements InternalFeature {
56+
57+
static class Options {
58+
@Option(help = "Specify the .json log file location for inferred dynamic accesses.")//
59+
static final HostedOptionKey<String> InferredDynamicAccessLog = new HostedOptionKey<>(null);
60+
}
61+
62+
private static final Queue<LogEntry> log = new ConcurrentLinkedQueue<>();
63+
64+
public static void logConstant(GraphBuilderContext b, ParsingReason reason, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments, Object value) {
65+
LogEntry entry = new ConstantLogEntry(b, targetMethod, targetReceiver, targetArguments, value);
66+
logEntry(b, reason, entry);
67+
}
68+
69+
public static void logException(GraphBuilderContext b, ParsingReason reason, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments,
70+
Class<? extends Throwable> exceptionClass) {
71+
LogEntry entry = new ExceptionLogEntry(b, targetMethod, targetReceiver, targetArguments, exceptionClass);
72+
logEntry(b, reason, entry);
73+
}
74+
75+
public static void logRegistration(GraphBuilderContext b, ParsingReason reason, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments) {
76+
LogEntry entry = new RegistreationLogEntry(b, targetMethod, targetReceiver, targetArguments);
77+
logEntry(b, reason, entry);
78+
}
79+
80+
private static void logEntry(GraphBuilderContext b, ParsingReason reason, LogEntry entry) {
81+
if (reason.duringAnalysis() && reason != ParsingReason.JITCompilation && isEnabled()) {
82+
b.add(ReachabilityRegistrationNode.create(() -> log.add(entry), reason));
83+
}
84+
}
85+
86+
private static boolean isEnabled() {
87+
return Options.InferredDynamicAccessLog.getValue() != null || shouldWarnForNonStrictFolding();
88+
}
89+
90+
@Override
91+
public boolean isInConfiguration(IsInConfigurationAccess access) {
92+
return isEnabled();
93+
}
94+
95+
@Override
96+
public void afterAnalysis(AfterAnalysisAccess access) {
97+
String logLocation = Options.InferredDynamicAccessLog.getValue();
98+
if (logLocation != null) {
99+
dump(logLocation);
100+
}
101+
if (shouldWarnForNonStrictFolding()) {
102+
warnForNonStrictFolding();
103+
}
104+
}
105+
106+
private static void dump(String location) {
107+
try (JsonWriter out = new JsonPrettyWriter(Path.of(location))) {
108+
try (JsonBuilder.ArrayBuilder arrayBuilder = out.arrayBuilder()) {
109+
for (LogEntry entry : log) {
110+
try (JsonBuilder.ObjectBuilder objectBuilder = arrayBuilder.nextEntry().object()) {
111+
entry.toJson(objectBuilder);
112+
}
113+
}
114+
}
115+
} catch (IOException e) {
116+
throw new RuntimeException(e);
117+
}
118+
}
119+
120+
private static boolean shouldWarnForNonStrictFolding() {
121+
return StrictConstantAnalysisFeature.Options.StrictConstantAnalysis.getValue() == StrictConstantAnalysisFeature.Options.Mode.Warn;
122+
}
123+
124+
private static void warnForNonStrictFolding() {
125+
ConstantExpressionRegistry registry = ImageSingletons.lookup(ConstantExpressionRegistry.class);
126+
List<LogEntry> unsafeFoldingEntries = log.stream().filter(entry -> !registryContainsConstantOperands(registry, entry)).toList();
127+
if (!unsafeFoldingEntries.isEmpty()) {
128+
StringBuilder sb = new StringBuilder();
129+
sb.append("The following method invocations have been inferred outside of the strict constant expression mode:").append(System.lineSeparator());
130+
for (int i = 0; i < unsafeFoldingEntries.size(); i++) {
131+
sb.append((i + 1)).append(". ").append(unsafeFoldingEntries.get(i));
132+
if (i != unsafeFoldingEntries.size() - 1) {
133+
sb.append(System.lineSeparator());
134+
}
135+
}
136+
LogUtils.warning(sb.toString());
137+
}
138+
}
139+
140+
private static boolean registryContainsConstantOperands(ConstantExpressionRegistry registry, LogEntry entry) {
141+
Pair<ResolvedJavaMethod, Integer> callLocation = entry.callLocation;
142+
if (entry.targetMethod.hasReceiver()) {
143+
Optional<Object> receiver = registry.getReceiver(callLocation.getLeft(), callLocation.getRight(), entry.targetMethod);
144+
if (entry.targetReceiver != IGNORED_ARGUMENT_MARKER && receiver.isEmpty()) {
145+
return false;
146+
}
147+
}
148+
for (int i = 0; i < entry.targetArguments.length; i++) {
149+
Optional<Object> argument = registry.getArgument(callLocation.getLeft(), callLocation.getRight(), entry.targetMethod, i);
150+
if (entry.targetArguments[i] != IGNORED_ARGUMENT_MARKER && argument.isEmpty()) {
151+
return false;
152+
}
153+
}
154+
return true;
155+
}
156+
157+
private static final Object IGNORED_ARGUMENT_MARKER = new IgnoredArgumentValue();
158+
159+
public static Object ignoredArgument() {
160+
return IGNORED_ARGUMENT_MARKER;
161+
}
162+
163+
private static final class IgnoredArgumentValue {
164+
165+
@Override
166+
public String toString() {
167+
return "<ignored>";
168+
}
169+
}
170+
171+
private abstract static class LogEntry {
172+
173+
private final Pair<ResolvedJavaMethod, Integer> callLocation;
174+
private final List<StackTraceElement> callStack;
175+
private final ResolvedJavaMethod targetMethod;
176+
private final Object targetReceiver;
177+
private final Object[] targetArguments;
178+
179+
LogEntry(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments) {
180+
if (targetMethod.hasReceiver() && targetReceiver == null || targetMethod.getSignature().getParameterCount(false) != targetArguments.length) {
181+
throw VMError.shouldNotReachHere("Inferred arguments do not match with target method signature");
182+
}
183+
this.callLocation = Pair.create(b.getMethod(), b.bci());
184+
this.callStack = b.getCallStack();
185+
this.targetMethod = targetMethod;
186+
this.targetReceiver = targetReceiver;
187+
this.targetArguments = targetArguments;
188+
}
189+
190+
@Override
191+
public String toString() {
192+
String targetArgumentsString = Stream.of(targetArguments)
193+
.map(arg -> arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg)).collect(Collectors.joining(", "));
194+
195+
if (targetReceiver != null) {
196+
return String.format("Call to %s reachable in %s with receiver %s and arguments (%s) was inferred",
197+
targetMethod.format("%H.%n(%p)"), callStack.getFirst(), targetReceiver, targetArgumentsString);
198+
} else {
199+
return String.format("Call to %s reachable in %s with arguments (%s) was inferred",
200+
targetMethod.format("%H.%n(%p)"), callStack.getFirst(), targetArgumentsString);
201+
}
202+
}
203+
204+
public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException {
205+
try (JsonBuilder.ArrayBuilder foldContextBuilder = builder.append("foldContext").array()) {
206+
for (StackTraceElement element : callStack) {
207+
foldContextBuilder.append(element);
208+
}
209+
}
210+
builder.append("targetMethod", targetMethod.format("%H.%n(%p)"));
211+
if (targetReceiver != null) {
212+
builder.append("targetCaller", targetReceiver);
213+
}
214+
try (JsonBuilder.ArrayBuilder argsBuilder = builder.append("targetArguments").array()) {
215+
for (Object arg : targetArguments) {
216+
argsBuilder.append(arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg));
217+
}
218+
}
219+
}
220+
}
221+
222+
private static class ConstantLogEntry extends LogEntry {
223+
224+
private final Object value;
225+
226+
ConstantLogEntry(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Object value) {
227+
super(b, targetMethod, targetCaller, targetArguments);
228+
this.value = value;
229+
}
230+
231+
@Override
232+
public String toString() {
233+
return super.toString() + " as the constant " + value;
234+
}
235+
236+
@Override
237+
public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException {
238+
super.toJson(builder);
239+
builder.append("constantValue", value);
240+
}
241+
}
242+
243+
private static class ExceptionLogEntry extends LogEntry {
244+
245+
private final Class<? extends Throwable> exceptionClass;
246+
247+
ExceptionLogEntry(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Class<? extends Throwable> exceptionClass) {
248+
super(b, targetMethod, targetCaller, targetArguments);
249+
this.exceptionClass = exceptionClass;
250+
}
251+
252+
@Override
253+
public String toString() {
254+
return super.toString() + " to throw " + exceptionClass.getName();
255+
}
256+
257+
@Override
258+
public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException {
259+
super.toJson(builder);
260+
builder.append("exception", exceptionClass.getName());
261+
}
262+
}
263+
264+
private static class RegistreationLogEntry extends LogEntry {
265+
266+
RegistreationLogEntry(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments) {
267+
super(b, targetMethod, targetCaller, targetArguments);
268+
}
269+
270+
@Override
271+
public String toString() {
272+
return super.toString() + " and registered for runtime usage";
273+
}
274+
275+
@Override
276+
public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException {
277+
super.toJson(builder);
278+
}
279+
}
280+
}

0 commit comments

Comments
 (0)