Skip to content

Commit 646b9db

Browse files
committed
Feature for logging build-time inferred method invocations otherwise requiring reachability registrations.
1 parent 1e3ace5 commit 646b9db

File tree

4 files changed

+313
-0
lines changed

4 files changed

+313
-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,278 @@
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.function.Supplier;
52+
import java.util.stream.Collectors;
53+
import java.util.stream.Stream;
54+
55+
@AutomaticallyRegisteredFeature
56+
public class InferredDynamicAccessLoggingFeature implements InternalFeature {
57+
58+
static class Options {
59+
@Option(help = "Specify the .json log file location for inferred dynamic accesses.")//
60+
static final HostedOptionKey<String> InferredDynamicAccessLog = new HostedOptionKey<>(null);
61+
}
62+
63+
private static final Queue<LogEntry> log = new ConcurrentLinkedQueue<>();
64+
65+
public static void logConstant(GraphBuilderContext b, ParsingReason reason, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments, Object value) {
66+
logEntry(b, reason, () -> new ConstantLogEntry(b, targetMethod, targetReceiver, targetArguments, value));
67+
}
68+
69+
public static void logException(GraphBuilderContext b, ParsingReason reason, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments,
70+
Class<? extends Throwable> exceptionClass) {
71+
logEntry(b, reason, () -> new ExceptionLogEntry(b, targetMethod, targetReceiver, targetArguments, exceptionClass));
72+
}
73+
74+
public static void logRegistration(GraphBuilderContext b, ParsingReason reason, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments) {
75+
logEntry(b, reason, () -> new RegistreationLogEntry(b, targetMethod, targetReceiver, targetArguments));
76+
}
77+
78+
private static void logEntry(GraphBuilderContext b, ParsingReason reason, Supplier<LogEntry> entrySupplier) {
79+
if (reason.duringAnalysis() && reason != ParsingReason.JITCompilation && isEnabled()) {
80+
LogEntry entry = entrySupplier.get();
81+
b.add(ReachabilityRegistrationNode.create(() -> log.add(entry), reason));
82+
}
83+
}
84+
85+
private static boolean isEnabled() {
86+
return Options.InferredDynamicAccessLog.getValue() != null || shouldWarnForNonStrictFolding();
87+
}
88+
89+
@Override
90+
public boolean isInConfiguration(IsInConfigurationAccess access) {
91+
return isEnabled();
92+
}
93+
94+
@Override
95+
public void afterAnalysis(AfterAnalysisAccess access) {
96+
String logLocation = Options.InferredDynamicAccessLog.getValue();
97+
if (logLocation != null) {
98+
dump(logLocation);
99+
}
100+
if (shouldWarnForNonStrictFolding()) {
101+
warnForNonStrictFolding();
102+
}
103+
}
104+
105+
private static void dump(String location) {
106+
try (JsonWriter out = new JsonPrettyWriter(Path.of(location))) {
107+
try (JsonBuilder.ArrayBuilder arrayBuilder = out.arrayBuilder()) {
108+
for (LogEntry entry : log) {
109+
try (JsonBuilder.ObjectBuilder objectBuilder = arrayBuilder.nextEntry().object()) {
110+
entry.toJson(objectBuilder);
111+
}
112+
}
113+
}
114+
} catch (IOException e) {
115+
throw new RuntimeException(e);
116+
}
117+
}
118+
119+
private static boolean shouldWarnForNonStrictFolding() {
120+
return StrictConstantAnalysisFeature.Options.StrictConstantAnalysis.getValue() == StrictConstantAnalysisFeature.Options.Mode.Warn;
121+
}
122+
123+
private static void warnForNonStrictFolding() {
124+
ConstantExpressionRegistry registry = ImageSingletons.lookup(ConstantExpressionRegistry.class);
125+
List<LogEntry> unsafeFoldingEntries = log.stream().filter(entry -> !registryContainsConstantOperands(registry, entry)).toList();
126+
if (!unsafeFoldingEntries.isEmpty()) {
127+
StringBuilder sb = new StringBuilder();
128+
sb.append("The following method invocations have been inferred outside of the strict constant expression mode:").append(System.lineSeparator());
129+
for (int i = 0; i < unsafeFoldingEntries.size(); i++) {
130+
sb.append((i + 1)).append(". ").append(unsafeFoldingEntries.get(i));
131+
if (i != unsafeFoldingEntries.size() - 1) {
132+
sb.append(System.lineSeparator());
133+
}
134+
}
135+
LogUtils.warning(sb.toString());
136+
}
137+
}
138+
139+
private static boolean registryContainsConstantOperands(ConstantExpressionRegistry registry, LogEntry entry) {
140+
Pair<ResolvedJavaMethod, Integer> callLocation = entry.callLocation;
141+
if (entry.targetMethod.hasReceiver()) {
142+
Optional<Object> receiver = registry.getReceiver(callLocation.getLeft(), callLocation.getRight(), entry.targetMethod);
143+
if (entry.targetReceiver != IGNORED_ARGUMENT_MARKER && receiver.isEmpty()) {
144+
return false;
145+
}
146+
}
147+
for (int i = 0; i < entry.targetArguments.length; i++) {
148+
Optional<Object> argument = registry.getArgument(callLocation.getLeft(), callLocation.getRight(), entry.targetMethod, i);
149+
if (entry.targetArguments[i] != IGNORED_ARGUMENT_MARKER && argument.isEmpty()) {
150+
return false;
151+
}
152+
}
153+
return true;
154+
}
155+
156+
private static final Object IGNORED_ARGUMENT_MARKER = new IgnoredArgumentValue();
157+
158+
public static Object ignoredArgument() {
159+
return IGNORED_ARGUMENT_MARKER;
160+
}
161+
162+
private static final class IgnoredArgumentValue {
163+
164+
@Override
165+
public String toString() {
166+
return "<ignored>";
167+
}
168+
}
169+
170+
private abstract static class LogEntry {
171+
172+
private final Pair<ResolvedJavaMethod, Integer> callLocation;
173+
private final List<StackTraceElement> callStack;
174+
private final ResolvedJavaMethod targetMethod;
175+
private final Object targetReceiver;
176+
private final Object[] targetArguments;
177+
178+
LogEntry(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments) {
179+
VMError.guarantee(targetMethod.hasReceiver() == (targetReceiver != null), "Inferred receiver does not match with target method signature");
180+
VMError.guarantee(targetMethod.getSignature().getParameterCount(false) == targetArguments.length, "Inferred arguments do not match with target method signature");
181+
this.callLocation = Pair.create(b.getMethod(), b.bci());
182+
this.callStack = b.getCallStack();
183+
this.targetMethod = targetMethod;
184+
this.targetReceiver = targetReceiver;
185+
this.targetArguments = targetArguments;
186+
}
187+
188+
@Override
189+
public String toString() {
190+
String targetArgumentsString = Stream.of(targetArguments)
191+
.map(arg -> arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg)).collect(Collectors.joining(", "));
192+
193+
if (targetReceiver != null) {
194+
return String.format("Call to %s reachable in %s with receiver %s and arguments (%s) was inferred",
195+
targetMethod.format("%H.%n(%p)"), callStack.getFirst(), targetReceiver, targetArgumentsString);
196+
} else {
197+
return String.format("Call to %s reachable in %s with arguments (%s) was inferred",
198+
targetMethod.format("%H.%n(%p)"), callStack.getFirst(), targetArgumentsString);
199+
}
200+
}
201+
202+
public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException {
203+
try (JsonBuilder.ArrayBuilder foldContextBuilder = builder.append("foldContext").array()) {
204+
for (StackTraceElement element : callStack) {
205+
foldContextBuilder.append(element);
206+
}
207+
}
208+
builder.append("targetMethod", targetMethod.format("%H.%n(%p)"));
209+
if (targetReceiver != null) {
210+
builder.append("targetCaller", targetReceiver);
211+
}
212+
try (JsonBuilder.ArrayBuilder argsBuilder = builder.append("targetArguments").array()) {
213+
for (Object arg : targetArguments) {
214+
argsBuilder.append(arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg));
215+
}
216+
}
217+
}
218+
}
219+
220+
private static class ConstantLogEntry extends LogEntry {
221+
222+
private final Object value;
223+
224+
ConstantLogEntry(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Object value) {
225+
super(b, targetMethod, targetCaller, targetArguments);
226+
this.value = value;
227+
}
228+
229+
@Override
230+
public String toString() {
231+
return super.toString() + " as the constant " + value;
232+
}
233+
234+
@Override
235+
public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException {
236+
super.toJson(builder);
237+
builder.append("constantValue", value);
238+
}
239+
}
240+
241+
private static class ExceptionLogEntry extends LogEntry {
242+
243+
private final Class<? extends Throwable> exceptionClass;
244+
245+
ExceptionLogEntry(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Class<? extends Throwable> exceptionClass) {
246+
super(b, targetMethod, targetCaller, targetArguments);
247+
this.exceptionClass = exceptionClass;
248+
}
249+
250+
@Override
251+
public String toString() {
252+
return super.toString() + " to throw " + exceptionClass.getName();
253+
}
254+
255+
@Override
256+
public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException {
257+
super.toJson(builder);
258+
builder.append("exception", exceptionClass.getName());
259+
}
260+
}
261+
262+
private static class RegistreationLogEntry extends LogEntry {
263+
264+
RegistreationLogEntry(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments) {
265+
super(b, targetMethod, targetCaller, targetArguments);
266+
}
267+
268+
@Override
269+
public String toString() {
270+
return super.toString() + " and registered for runtime usage";
271+
}
272+
273+
@Override
274+
public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException {
275+
super.toJson(builder);
276+
}
277+
}
278+
}

0 commit comments

Comments
 (0)