Skip to content

Commit 009a6f9

Browse files
committed
[GR-61494] [GR-60855] Analysis Refactoring & Minor Improvements
PullRequest: graal/19846
2 parents 2e77b7e + 402151d commit 009a6f9

File tree

16 files changed

+88
-113
lines changed

16 files changed

+88
-113
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,8 +376,12 @@ public AnalysisMethod addRootMethod(AnalysisMethod aMethod, boolean invokeSpecia
376376
* initialized with the corresponding parameter declared type.
377377
*/
378378
Consumer<PointsToAnalysisMethod> triggerStaticMethodFlow = (pointsToMethod) -> {
379+
/*
380+
* Make sure that the method is registered as root immediately, so that a potential
381+
* subsequent registration as native entrypoint does not fail.
382+
*/
383+
pointsToMethod.registerAsDirectRootMethod(reason);
379384
postTask(() -> {
380-
pointsToMethod.registerAsDirectRootMethod(reason);
381385
pointsToMethod.registerAsImplementationInvoked(reason.toString());
382386
MethodFlowsGraphInfo flowInfo = analysisPolicy.staticRootMethodGraph(this, pointsToMethod);
383387
for (int idx = 0; idx < paramCount; idx++) {

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/constraints/ShortestInvokeChainPrinter.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@
3232

3333
import com.oracle.graal.pointsto.BigBang;
3434
import com.oracle.graal.pointsto.meta.AnalysisMethod;
35-
35+
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
3636
import com.oracle.graal.pointsto.meta.InvokeInfo;
37+
3738
import jdk.vm.ci.code.BytecodePosition;
3839

3940
public final class ShortestInvokeChainPrinter {
@@ -59,11 +60,9 @@ public static void print(BigBang bb, AnalysisMethod target, PrintStream out) {
5960
Deque<AnalysisMethod> workList = new LinkedList<>();
6061
Map<AnalysisMethod, Element> visited = new HashMap<>();
6162

62-
for (AnalysisMethod m : bb.getUniverse().getMethods()) {
63-
if (m.isEntryPoint()) {
64-
workList.addLast(m);
65-
visited.put(m, new Element(m, null, null));
66-
}
63+
for (AnalysisMethod m : AnalysisUniverse.getCallTreeRoots(bb.getUniverse())) {
64+
workList.addLast(m);
65+
visited.put(m, new Element(m, null, null));
6766
}
6867

6968
while (workList.size() > 0) {

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public record Signature(String name, AnalysisType[] parameterTypes) {
161161
@SuppressWarnings("unused") private volatile Object isVirtualRootMethod;
162162
/** Direct (special or static) invoked method registered as root. */
163163
@SuppressWarnings("unused") private volatile Object isDirectRootMethod;
164-
private Object entryPointData;
164+
private Object nativeEntryPointData;
165165
@SuppressWarnings("unused") private volatile Object isInvoked;
166166
@SuppressWarnings("unused") private volatile Object isImplementationInvoked;
167167
/**
@@ -490,12 +490,17 @@ public void registerAsIntrinsicMethod(Object reason) {
490490
AtomicUtils.atomicSetAndRun(this, reason, isIntrinsicMethodUpdater, () -> onImplementationInvoked(reason));
491491
}
492492

493-
public void registerAsEntryPoint(Object newEntryPointData) {
493+
/**
494+
* Registers this method as a native entrypoint, i.e. a method callable from the host
495+
* environment. Only direct root methods can be registered as entrypoints.
496+
*/
497+
public void registerAsNativeEntryPoint(Object newEntryPointData) {
494498
assert newEntryPointData != null;
495-
if (entryPointData != null && !entryPointData.equals(newEntryPointData)) {
496-
throw new UnsupportedFeatureException("Method is registered as entry point with conflicting entry point data: " + entryPointData + ", " + newEntryPointData);
499+
assert isDirectRootMethod() : "All native entrypoints must be direct root methods: " + this;
500+
if (nativeEntryPointData != null && !nativeEntryPointData.equals(newEntryPointData)) {
501+
throw new UnsupportedFeatureException("Method is registered as entry point with conflicting entry point data: " + nativeEntryPointData + ", " + newEntryPointData);
497502
}
498-
entryPointData = newEntryPointData;
503+
nativeEntryPointData = newEntryPointData;
499504
/* We need that to check that entry points are not invoked from other Java methods. */
500505
startTrackInvocations();
501506
}
@@ -566,12 +571,16 @@ public Set<AnalysisMethod> getCallers() {
566571
/** Get the list of all invoke locations for this method, as inferred by the static analysis. */
567572
public abstract List<BytecodePosition> getInvokeLocations();
568573

569-
public boolean isEntryPoint() {
570-
return entryPointData != null;
574+
/**
575+
* Returns true if this method is a native entrypoint, i.e. it may be called from the host
576+
* environment.
577+
*/
578+
public boolean isNativeEntryPoint() {
579+
return nativeEntryPointData != null;
571580
}
572581

573-
public Object getEntryPointData() {
574-
return entryPointData;
582+
public Object getNativeEntryPointData() {
583+
return nativeEntryPointData;
575584
}
576585

577586
public boolean isIntrinsicMethod() {
@@ -597,7 +606,10 @@ public boolean registerAsVirtualRootMethod(Object reason) {
597606
}
598607

599608
/**
600-
* Registers this method as a direct (special or static) root for the analysis.
609+
* Registers this method as a direct (special or static) root for the analysis. Note that for
610+
* `invokespecial` direct roots, this <b>does not</b> guarantee that the method is
611+
* implementation invoked, as that registration is delayed until a suitable receiver type is
612+
* marked as instantiated.
601613
*/
602614
public boolean registerAsDirectRootMethod(Object reason) {
603615
getDeclaringClass().registerAsReachable("declared method " + qualifiedName + " is registered as direct root");
@@ -909,7 +921,7 @@ public LineNumberTable getLineNumberTable() {
909921
public String toString() {
910922
return "AnalysisMethod<" + format("%h.%n") + " -> " + wrapped.toString() + ", invoked: " + (isInvoked != null) +
911923
", implInvoked: " + (isImplementationInvoked != null) + ", intrinsic: " + (isIntrinsicMethod != null) + ", inlined: " + (isInlined != null) +
912-
(isVirtualRootMethod() ? ", virtual root" : "") + (isDirectRootMethod() ? ", direct root" : "") + (isEntryPoint() ? ", entry point" : "") + ">";
924+
(isVirtualRootMethod() ? ", virtual root" : "") + (isDirectRootMethod() ? ", direct root" : "") + (isNativeEntryPoint() ? ", entry point" : "") + ">";
913925
}
914926

915927
@Override

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/CallTreePrinter.java

Lines changed: 35 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.Arrays;
4343
import java.util.Collection;
4444
import java.util.Deque;
45+
import java.util.HashMap;
4546
import java.util.HashSet;
4647
import java.util.Iterator;
4748
import java.util.LinkedHashMap;
@@ -355,18 +356,16 @@ private static String packagePrefix(String name) {
355356
}
356357

357358
private static void printCsvFiles(Map<AnalysisMethod, MethodNode> methodToNode, String reportsPath, String reportName) {
358-
Set<MethodNode> nodes = new HashSet<>();
359-
360-
List<MethodNode> entrypoints = methodToNode.values().stream().filter(n -> n.isEntryPoint).toList();
361-
for (MethodNode entrypoint : entrypoints) {
362-
walkNodes(entrypoint, nodes, methodToNode);
363-
}
364-
365359
String msgPrefix = "call tree csv file for ";
366360
String timeStamp = ReportUtils.getTimeStampString();
367-
toCsvFile(msgPrefix + "methods", reportsPath, "call_tree_methods", reportName, timeStamp, writer -> printMethodNodes(methodToNode.values(), writer));
361+
/*
362+
* We print invokes first, because when traversing the invokes new method nodes (for call
363+
* targets that were not visited before, e.g. abstract methods) may be created, which we
364+
* want to print in call_tree_methods as well.
365+
*/
368366
toCsvFile(msgPrefix + "invokes", reportsPath, "call_tree_invokes", reportName, timeStamp, writer -> printInvokeNodes(methodToNode, writer));
369367
toCsvFile(msgPrefix + "targets", reportsPath, "call_tree_targets", reportName, timeStamp, writer -> printCallTargets(methodToNode, writer));
368+
toCsvFile(msgPrefix + "methods", reportsPath, "call_tree_methods", reportName, timeStamp, writer -> printMethodNodes(methodToNode.values(), writer));
370369
}
371370

372371
private static void toCsvFile(String description, String reportsPath, String prefix, String reportName, String timeStamp, Consumer<PrintWriter> reporter) {
@@ -399,12 +398,20 @@ private static void printMethodNodes(Collection<MethodNode> methods, PrintWriter
399398

400399
private static void printInvokeNodes(Map<AnalysisMethod, MethodNode> methodToNode, PrintWriter writer) {
401400
writer.println(convertToCSV("Id", "MethodId", "BytecodeIndexes", "TargetId", "IsDirect"));
401+
/*
402+
* Methods that act as call targets, but are not reachable (e.g. abstract methods), will not
403+
* have a MethodNode allocated yet. We store them in a separate map, because methodToNode
404+
* cannot be modified while we iterate over it.
405+
*/
406+
var callTargets = new HashMap<AnalysisMethod, MethodNode>();
402407
methodToNode.values().stream()
403408
.flatMap(node -> node.invokes.stream()
404-
.filter(invoke -> !invoke.callees.isEmpty())
405-
.map(invoke -> invokeNodeInfo(methodToNode, node, invoke)))
409+
.map(invoke -> invokeNodeInfo(methodToNode, node, invoke, callTargets)))
406410
.map(CallTreePrinter::convertToCSV)
407411
.forEach(writer::println);
412+
for (var entry : callTargets.entrySet()) {
413+
methodToNode.putIfAbsent(entry.getKey(), entry.getValue());
414+
}
408415
}
409416

410417
private static void printCallTargets(Map<AnalysisMethod, MethodNode> methodToNode, PrintWriter writer) {
@@ -419,15 +426,30 @@ private static void printCallTargets(Map<AnalysisMethod, MethodNode> methodToNod
419426
}
420427

421428
private static List<String> methodNodeInfo(MethodNode method) {
422-
return resolvedJavaMethodInfo(method.id, method.method);
429+
var parameters = method.method.getSignature().getParameterCount(false) > 0
430+
? method.method.format("%P").replace(",", "")
431+
: "empty";
432+
return Arrays.asList(
433+
Integer.toString(method.id),
434+
method.method.getName(),
435+
method.method.getDeclaringClass().toJavaName(true),
436+
parameters,
437+
method.method.getSignature().getReturnType().toJavaName(true),
438+
display(method.method),
439+
flags(method.method),
440+
String.valueOf(method.isEntryPoint));
423441
}
424442

425-
private static List<String> invokeNodeInfo(Map<AnalysisMethod, MethodNode> methodToNode, MethodNode method, InvokeNode invoke) {
443+
private static List<String> invokeNodeInfo(Map<AnalysisMethod, MethodNode> methodToNode, MethodNode method, InvokeNode invoke, HashMap<AnalysisMethod, MethodNode> callTargets) {
444+
MethodNode targetMethod = methodToNode.get(invoke.targetMethod);
445+
if (targetMethod == null) {
446+
targetMethod = callTargets.computeIfAbsent(invoke.targetMethod, MethodNode::new);
447+
}
426448
return Arrays.asList(
427449
String.valueOf(invoke.id),
428450
String.valueOf(method.id),
429451
showBytecodeIndexes(bytecodeIndexes(invoke)),
430-
String.valueOf(methodToNode.get(invoke.targetMethod).id),
452+
String.valueOf(targetMethod.id),
431453
String.valueOf(invoke.isDirectInvoke));
432454
}
433455

@@ -436,34 +458,6 @@ private static List<String> callTargetInfo(InvokeNode invoke, Node callee) {
436458
return Arrays.asList(String.valueOf(invoke.id), String.valueOf(node.id));
437459
}
438460

439-
private static void walkNodes(MethodNode methodNode, Set<MethodNode> nodes, Map<AnalysisMethod, MethodNode> methodToNode) {
440-
for (InvokeNode invoke : methodNode.invokes) {
441-
methodToNode.computeIfAbsent(invoke.targetMethod, MethodNode::new);
442-
if (invoke.isDirectInvoke) {
443-
if (invoke.callees.size() > 0) {
444-
Node calleeNode = invoke.callees.get(0);
445-
addNode(calleeNode, nodes);
446-
if (calleeNode instanceof MethodNode) {
447-
walkNodes((MethodNode) calleeNode, nodes, methodToNode);
448-
}
449-
}
450-
} else {
451-
for (Node calleeNode : invoke.callees) {
452-
if (calleeNode instanceof MethodNode) {
453-
walkNodes((MethodNode) calleeNode, nodes, methodToNode);
454-
}
455-
}
456-
}
457-
}
458-
}
459-
460-
private static void addNode(Node calleeNode, Set<MethodNode> nodes) {
461-
MethodNode methodNode = calleeNode instanceof MethodNode
462-
? (MethodNode) calleeNode
463-
: ((MethodNodeReference) calleeNode).methodNode;
464-
nodes.add(methodNode);
465-
}
466-
467461
private static List<Integer> bytecodeIndexes(InvokeNode node) {
468462
return Stream.of(node.sourceReferences)
469463
.map(source -> source.bci)
@@ -476,28 +470,6 @@ private static String showBytecodeIndexes(List<Integer> bytecodeIndexes) {
476470
.collect(Collectors.joining("->"));
477471
}
478472

479-
private static List<String> resolvedJavaMethodInfo(Integer id, AnalysisMethod method) {
480-
// TODO method parameter types are opaque, but could in the future be split out and link
481-
// together
482-
// e.g. each method could BELONG to a type, and a method could have PARAMETER relationships
483-
// with N types
484-
// see https://neo4j.com/developer/guide-import-csv/#_converting_data_values_with_load_csv
485-
// for examples
486-
final String parameters = method.getSignature().getParameterCount(false) > 0
487-
? method.format("%P").replace(",", "")
488-
: "empty";
489-
490-
return Arrays.asList(
491-
id == null ? null : Integer.toString(id),
492-
method.getName(),
493-
method.getDeclaringClass().toJavaName(true),
494-
parameters,
495-
method.getSignature().getReturnType().toJavaName(true),
496-
display(method),
497-
flags(method),
498-
String.valueOf(method.isEntryPoint()));
499-
}
500-
501473
private static String display(AnalysisMethod method) {
502474
final AnalysisType type = method.getDeclaringClass();
503475
final String typeName = type.toJavaName(true);

substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -327,23 +327,10 @@ private void computeCallers() {
327327
Set<ReachabilityAnalysisMethod> seen = new HashSet<>();
328328
Deque<ReachabilityAnalysisMethod> queue = new ArrayDeque<>();
329329

330-
for (AnalysisMethod m : universe.getMethods()) {
331-
ReachabilityAnalysisMethod method = ((ReachabilityAnalysisMethod) m);
332-
if (method.isDirectRootMethod() || method.isEntryPoint()) {
333-
if (seen.add(method)) {
334-
queue.add(method);
335-
}
336-
}
337-
if (method.isVirtualRootMethod()) {
338-
for (ReachabilityAnalysisType subtype : method.getDeclaringClass().getInstantiatedSubtypes()) {
339-
ReachabilityAnalysisMethod resolved = subtype.resolveConcreteMethod(method, subtype);
340-
if (resolved != null) {
341-
if (seen.add(resolved)) {
342-
queue.add(resolved);
343-
}
344-
}
345-
}
346-
}
330+
for (AnalysisMethod m : AnalysisUniverse.getCallTreeRoots(getUniverse())) {
331+
ReachabilityAnalysisMethod method = (ReachabilityAnalysisMethod) m;
332+
queue.add(method);
333+
seen.add(method);
347334
}
348335

349336
while (!queue.isEmpty()) {

substratevm/src/com.oracle.svm.core.graal.llvm/src/com/oracle/svm/core/graal/llvm/LLVMGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ private void addMainFunction(ResolvedJavaMethod method) {
250250
if (isEntryPoint) {
251251
builder.addAlias(SubstrateUtil.mangleName(functionName));
252252

253-
Object entryPointData = ((HostedMethod) method).getWrapped().getEntryPointData();
253+
Object entryPointData = ((HostedMethod) method).getWrapped().getNativeEntryPointData();
254254
if (entryPointData instanceof CEntryPointData) {
255255
CEntryPointData cEntryPointData = (CEntryPointData) entryPointData;
256256
if (cEntryPointData.getPublishAs() != CEntryPoint.Publish.NotPublished) {

substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,12 @@ public SubstrateMethod(AnalysisMethod original, ImageCodeInfo codeInfo, HostedSt
134134
hashCode = original.hashCode();
135135
encodedGraphStartOffset = -1;
136136

137-
SubstrateCallingConventionKind callingConventionKind = ExplicitCallingConvention.Util.getCallingConventionKind(original, original.isEntryPoint());
137+
SubstrateCallingConventionKind callingConventionKind = ExplicitCallingConvention.Util.getCallingConventionKind(original, original.isNativeEntryPoint());
138138
flags = makeFlag(original.isBridge(), FLAG_BIT_BRIDGE) |
139139
makeFlag(original.hasNeverInlineDirective(), FLAG_BIT_NEVER_INLINE) |
140140
makeFlag(Uninterruptible.Utils.isUninterruptible(original), FLAG_BIT_UNINTERRUPTIBLE) |
141141
makeFlag(SubstrateSafepointInsertionPhase.needSafepointCheck(original), FLAG_BIT_NEEDS_SAFEPOINT_CHECK) |
142-
makeFlag(original.isEntryPoint(), FLAG_BIT_ENTRY_POINT) |
142+
makeFlag(original.isNativeEntryPoint(), FLAG_BIT_ENTRY_POINT) |
143143
makeFlag(original.isAnnotationPresent(Snippet.class), FLAG_BIT_SNIPPET) |
144144
makeFlag(original.isAnnotationPresent(SubstrateForeignCallTarget.class), FLAG_BIT_FOREIGN_CALL_TARGET) |
145145
makeFlag(callingConventionKind.ordinal(), FLAG_BIT_CALLING_CONVENTION_KIND, NUM_BITS_CALLING_CONVENTION_KIND) |

substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ private static <S, T, U extends ResolvedJavaMethod> Map<S, U> createStubs(
413413
AnalysisMethod analysisStub = access.getUniverse().lookup(stub);
414414
access.getBigBang().addRootMethod(analysisStub, false, "Foreign stub, registered in " + ForeignFunctionsFeature.class);
415415
if (registerAsEntryPoints) {
416-
analysisStub.registerAsEntryPoint(CEntryPointData.createCustomUnpublished());
416+
analysisStub.registerAsNativeEntryPoint(CEntryPointData.createCustomUnpublished());
417417
}
418418
created.put(key, stub);
419419
factory.registerStub(key, new MethodPointer(analysisStub));

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ protected void doRun(Map<Method, CEntryPointData> entryPoints, JavaMainSupport j
612612

613613
/* Find the entry point methods in the hosted world. */
614614
for (AnalysisMethod m : aUniverse.getMethods()) {
615-
if (m.isEntryPoint()) {
615+
if (m.isNativeEntryPoint()) {
616616
HostedMethod found = hUniverse.lookup(m);
617617
assert found != null;
618618
hostedEntryPoints.add(found);
@@ -1683,7 +1683,7 @@ private void checkUniverse() {
16831683
*/
16841684
protected void checkForInvalidCallsToEntryPoints() {
16851685
for (AnalysisMethod method : aUniverse.getMethods()) {
1686-
if (method.isEntryPoint()) {
1686+
if (method.isNativeEntryPoint()) {
16871687
Set<AnalysisMethod> invocations = method.getCallers();
16881688
if (invocations.size() > 0) {
16891689
String name = method.format("%H.%n(%p)");

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ protected void optimizeAfterParsing(BigBang bb, AnalysisMethod method, Structure
738738

739739
@Override
740740
public void methodBeforeTypeFlowCreationHook(BigBang bb, AnalysisMethod method, StructuredGraph graph) {
741-
if (method.isEntryPoint() && !Modifier.isStatic(graph.method().getModifiers())) {
741+
if (method.isNativeEntryPoint() && !Modifier.isStatic(graph.method().getModifiers())) {
742742
ValueNode receiver = graph.start().stateAfter().localAt(0);
743743
if (receiver != null && receiver.hasUsages()) {
744744
/*

0 commit comments

Comments
 (0)