Skip to content

Commit cce3830

Browse files
committed
Split the StrengthenSimplifier into a ReachabilitySimplifier and TypeFlowSimplifier.
1 parent d8ab154 commit cce3830

File tree

3 files changed

+1077
-896
lines changed

3 files changed

+1077
-896
lines changed
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
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.graal.pointsto.results;
26+
27+
import java.util.function.Function;
28+
import java.util.function.Supplier;
29+
30+
import com.oracle.graal.pointsto.heap.ImageHeapConstant;
31+
import com.oracle.graal.pointsto.meta.AnalysisField;
32+
import com.oracle.graal.pointsto.meta.AnalysisMethod;
33+
import com.oracle.graal.pointsto.meta.AnalysisType;
34+
import com.oracle.graal.pointsto.util.AnalysisError;
35+
36+
import jdk.graal.compiler.core.common.type.AbstractObjectStamp;
37+
import jdk.graal.compiler.core.common.type.ObjectStamp;
38+
import jdk.graal.compiler.core.common.type.Stamp;
39+
import jdk.graal.compiler.core.common.type.StampFactory;
40+
import jdk.graal.compiler.core.common.type.TypeReference;
41+
import jdk.graal.compiler.graph.Node;
42+
import jdk.graal.compiler.nodes.CallTargetNode;
43+
import jdk.graal.compiler.nodes.ConstantNode;
44+
import jdk.graal.compiler.nodes.FixedNode;
45+
import jdk.graal.compiler.nodes.FixedWithNextNode;
46+
import jdk.graal.compiler.nodes.FrameState;
47+
import jdk.graal.compiler.nodes.Invoke;
48+
import jdk.graal.compiler.nodes.LogicConstantNode;
49+
import jdk.graal.compiler.nodes.LogicNode;
50+
import jdk.graal.compiler.nodes.NodeView;
51+
import jdk.graal.compiler.nodes.PhiNode;
52+
import jdk.graal.compiler.nodes.PiNode;
53+
import jdk.graal.compiler.nodes.StructuredGraph;
54+
import jdk.graal.compiler.nodes.ValueNode;
55+
import jdk.graal.compiler.nodes.extended.BytecodeExceptionNode;
56+
import jdk.graal.compiler.nodes.extended.FieldOffsetProvider;
57+
import jdk.graal.compiler.nodes.java.ClassIsAssignableFromNode;
58+
import jdk.graal.compiler.nodes.java.InstanceOfNode;
59+
import jdk.graal.compiler.nodes.java.MethodCallTargetNode;
60+
import jdk.graal.compiler.nodes.spi.CoreProviders;
61+
import jdk.graal.compiler.nodes.spi.LimitedValueProxy;
62+
import jdk.graal.compiler.nodes.spi.SimplifierTool;
63+
import jdk.graal.compiler.nodes.util.GraphUtil;
64+
import jdk.graal.compiler.phases.common.CanonicalizerPhase.CustomSimplification;
65+
import jdk.graal.compiler.phases.common.inlining.InliningUtil;
66+
import jdk.graal.compiler.replacements.nodes.MacroInvokable;
67+
import jdk.vm.ci.meta.JavaKind;
68+
import jdk.vm.ci.meta.ResolvedJavaType;
69+
70+
/**
71+
* Simplify graphs based on reachability information tracked by the static analysis.
72+
*/
73+
class ReachabilitySimplifier implements CustomSimplification {
74+
75+
protected final StrengthenGraphs strengthenGraphs;
76+
protected final StructuredGraph graph;
77+
78+
/**
79+
* For runtime compiled methods, we must be careful to ensure new SubstrateTypes are not created
80+
* due to the optimizations performed during the
81+
* {@link StrengthenGraphs.AnalysisStrengthenGraphsPhase}.
82+
*/
83+
protected final Function<AnalysisType, ResolvedJavaType> toTargetFunction;
84+
85+
ReachabilitySimplifier(StrengthenGraphs strengthenGraphs, AnalysisMethod method, StructuredGraph graph) {
86+
this.strengthenGraphs = strengthenGraphs;
87+
this.graph = graph;
88+
this.toTargetFunction = strengthenGraphs.bb.getHostVM().getStrengthenGraphsToTargetFunction(method.getMultiMethodKey());
89+
}
90+
91+
@Override
92+
public void simplify(Node n, SimplifierTool tool) {
93+
if (n instanceof ValueNode node) {
94+
tryImproveStamp(node, tool);
95+
}
96+
97+
if (strengthenGraphs.simplifyDelegate(n, tool)) {
98+
// Handled in the delegate simplification.
99+
return;
100+
}
101+
102+
switch (n) {
103+
case InstanceOfNode node -> handleInstanceOf(node, tool);
104+
case ClassIsAssignableFromNode node -> handleClassIsAssignableFrom(node, tool);
105+
case BytecodeExceptionNode node -> handleBytecodeException(node, tool);
106+
case FrameState node -> handleFrameState(node);
107+
case PiNode node -> handlePi(node, tool);
108+
case Invoke invoke -> handleInvoke(invoke, tool);
109+
case null, default -> {
110+
}
111+
}
112+
}
113+
114+
protected void tryImproveStamp(ValueNode node, SimplifierTool tool) {
115+
if (!(node instanceof LimitedValueProxy) && !(node instanceof PhiNode) && !(node instanceof MacroInvokable)) {
116+
/*
117+
* The stamp of proxy nodes and phi nodes is inferred automatically, so we do not need
118+
* to improve them. Macro nodes prohibit changing their stamp because it is derived from
119+
* the macro's fallback invoke. First ask the node to improve the stamp itself, to
120+
* incorporate already improved input stamps.
121+
*/
122+
node.inferStamp();
123+
/*
124+
* Since this new stamp is not based on a type flow, it is valid for the entire method
125+
* and we can update the stamp of the node directly. We do not need an anchored PiNode.
126+
*/
127+
updateStampInPlace(node, strengthenStamp(node.stamp(NodeView.DEFAULT)), tool);
128+
}
129+
}
130+
131+
protected void updateStampInPlace(ValueNode node, Stamp newStamp, SimplifierTool tool) {
132+
if (newStamp != null) {
133+
Stamp oldStamp = node.stamp(NodeView.DEFAULT);
134+
Stamp computedStamp = oldStamp.improveWith(newStamp);
135+
if (!oldStamp.equals(computedStamp)) {
136+
node.setStamp(newStamp);
137+
tool.addToWorkList(node.usages());
138+
}
139+
}
140+
}
141+
142+
protected void handleInstanceOf(InstanceOfNode node, SimplifierTool tool) {
143+
ObjectStamp oldStamp = node.getCheckedStamp();
144+
Stamp newStamp = strengthenStamp(oldStamp);
145+
if (newStamp != null) {
146+
LogicNode replacement = graph.addOrUniqueWithInputs(InstanceOfNode.createHelper((ObjectStamp) oldStamp.improveWith(newStamp), node.getValue(), node.profile(), node.getAnchor()));
147+
/*
148+
* GR-59681: Once isAssignable is implemented for BaseLayerType, this check can be
149+
* removed
150+
*/
151+
AnalysisError.guarantee(node != replacement, "The new stamp needs to be different from the old stamp");
152+
node.replaceAndDelete(replacement);
153+
tool.addToWorkList(replacement);
154+
} else {
155+
strengthenGraphs.maybeAssignInstanceOfProfiles(node);
156+
}
157+
}
158+
159+
protected void handleClassIsAssignableFrom(ClassIsAssignableFromNode node, SimplifierTool tool) {
160+
if (strengthenGraphs.isClosedTypeWorld) {
161+
/*
162+
* If the constant receiver of a Class#isAssignableFrom is an unreachable type we can
163+
* constant-fold the ClassIsAssignableFromNode to false. See also
164+
* MethodTypeFlowBuilder#ignoreConstant where we avoid marking the corresponding type as
165+
* reachable just because it is used by the ClassIsAssignableFromNode. We only apply
166+
* this optimization if it's a closed type world, for open world we cannot fold the type
167+
* check since the type may be used later.
168+
*/
169+
AnalysisType nonReachableType = asConstantNonReachableType(node.getThisClass(), tool);
170+
if (nonReachableType != null) {
171+
node.replaceAndDelete(LogicConstantNode.contradiction(graph));
172+
}
173+
}
174+
}
175+
176+
protected void handleBytecodeException(BytecodeExceptionNode node, SimplifierTool tool) {
177+
/*
178+
* We do not want a type to be reachable only to be used for the error message of a
179+
* ClassCastException. Therefore, in that case we replace the java.lang.Class with a
180+
* java.lang.String that is then used directly in the error message. We can apply this
181+
* optimization optimistically for both closed and open type world.
182+
*/
183+
if (node.getExceptionKind() == BytecodeExceptionNode.BytecodeExceptionKind.CLASS_CAST) {
184+
AnalysisType nonReachableType = asConstantNonReachableType(node.getArguments().get(1), tool);
185+
if (nonReachableType != null) {
186+
node.getArguments().set(1, ConstantNode.forConstant(tool.getConstantReflection().forString(strengthenGraphs.getTypeName(nonReachableType)), tool.getMetaAccess(), graph));
187+
}
188+
}
189+
}
190+
191+
private static AnalysisType asConstantNonReachableType(ValueNode value, CoreProviders providers) {
192+
if (value != null && value.isConstant()) {
193+
AnalysisType expectedType = (AnalysisType) providers.getConstantReflection().asJavaType(value.asConstant());
194+
if (expectedType != null && !expectedType.isReachable()) {
195+
return expectedType;
196+
}
197+
}
198+
return null;
199+
}
200+
201+
protected void handleFrameState(FrameState node) {
202+
/*
203+
* We do not want a constant to be reachable only to be used for debugging purposes in a
204+
* FrameState.
205+
*/
206+
for (int i = 0; i < node.values().size(); i++) {
207+
if (node.values().get(i) instanceof ConstantNode constantNode && constantNode.getValue() instanceof ImageHeapConstant imageHeapConstant && !imageHeapConstant.isReachable()) {
208+
node.values().set(i, ConstantNode.defaultForKind(JavaKind.Object, graph));
209+
}
210+
if (node.values().get(i) instanceof FieldOffsetProvider fieldOffsetProvider && !((AnalysisField) fieldOffsetProvider.getField()).isUnsafeAccessed()) {
211+
/*
212+
* We use a unique marker constant as the replacement value, so that a search in the
213+
* code base for the value leads us to here.
214+
*/
215+
node.values().set(i, ConstantNode.forIntegerKind(fieldOffsetProvider.asNode().getStackKind(), 0xDEA51106, graph));
216+
}
217+
}
218+
}
219+
220+
protected void handlePi(PiNode node, SimplifierTool tool) {
221+
Stamp oldStamp = node.piStamp();
222+
Stamp newStamp = strengthenStamp(oldStamp);
223+
if (newStamp != null) {
224+
Stamp newPiStamp = oldStamp.improveWith(newStamp);
225+
/*
226+
* GR-59681: Once isAssignable is implemented for BaseLayerType, this check can be
227+
* removed
228+
*/
229+
AnalysisError.guarantee(!newPiStamp.equals(oldStamp), "The new stamp needs to be different from the old stamp");
230+
node.strengthenPiStamp(newPiStamp);
231+
tool.addToWorkList(node);
232+
}
233+
}
234+
235+
private void handleInvoke(Invoke invoke, SimplifierTool tool) {
236+
if (invoke.callTarget() instanceof MethodCallTargetNode) {
237+
maybeMarkUnreachable(invoke, tool);
238+
}
239+
}
240+
241+
protected boolean maybeMarkUnreachable(Invoke invoke, SimplifierTool tool) {
242+
MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget();
243+
AnalysisMethod targetMethod = (AnalysisMethod) callTarget.targetMethod();
244+
if (callTarget.invokeKind().isDirect() && !targetMethod.isSimplyImplementationInvoked()) {
245+
/*
246+
* This is a direct call to a method that the static analysis did not see as invoked.
247+
* This can happen when the receiver is always null. In most cases, the method profile
248+
* also has a length of 0 and the below code to kill the invoke would trigger. But when
249+
* only running the reachability analysis, there is no detailed list of callees.
250+
*/
251+
unreachableInvoke(invoke, tool, () -> location(invoke) + ": target method is not marked as simply implementation invoked");
252+
return true;
253+
}
254+
return false;
255+
}
256+
257+
/**
258+
* The invoke has no callee, i.e., it is unreachable.
259+
*/
260+
protected void unreachableInvoke(Invoke invoke, SimplifierTool tool, Supplier<String> messageSupplier) {
261+
if (invoke.getInvokeKind() != CallTargetNode.InvokeKind.Static) {
262+
/*
263+
* Ensure that a null check for the receiver remains in the graph. There should be
264+
* already an explicit null check in the graph, but we are paranoid and check again.
265+
*/
266+
InliningUtil.nonNullReceiver(invoke);
267+
}
268+
269+
makeUnreachable(invoke.asFixedNode(), tool, messageSupplier);
270+
}
271+
272+
protected void makeUnreachable(FixedNode node, CoreProviders providers, Supplier<String> message) {
273+
FixedNode unreachableNode = strengthenGraphs.createUnreachable(graph, providers, message);
274+
((FixedWithNextNode) node.predecessor()).setNext(unreachableNode);
275+
GraphUtil.killCFG(node);
276+
}
277+
278+
protected String location(Invoke invoke) {
279+
return "method " + StrengthenGraphs.getQualifiedName(graph) + ", node " + invoke;
280+
}
281+
282+
protected String location(Node node) {
283+
return "method " + StrengthenGraphs.getQualifiedName(graph) + ", node " + node;
284+
}
285+
286+
private Stamp strengthenStamp(Stamp s) {
287+
if (!(s instanceof AbstractObjectStamp stamp)) {
288+
return null;
289+
}
290+
AnalysisType originalType = (AnalysisType) stamp.type();
291+
if (originalType == null) {
292+
return null;
293+
}
294+
295+
/* In open world the type may become reachable later. */
296+
if (strengthenGraphs.isClosedTypeWorld && !originalType.isReachable()) {
297+
/* We must be in dead code. */
298+
if (stamp.nonNull()) {
299+
/* We must be in dead code. */
300+
return StampFactory.empty(JavaKind.Object);
301+
} else {
302+
return StampFactory.alwaysNull();
303+
}
304+
}
305+
306+
AnalysisType singleImplementorType = strengthenGraphs.getSingleImplementorType(originalType);
307+
if (singleImplementorType != null && (!stamp.isExactType() || !singleImplementorType.equals(originalType))) {
308+
ResolvedJavaType targetType = toTargetFunction.apply(singleImplementorType);
309+
if (targetType != null) {
310+
TypeReference typeRef = TypeReference.createExactTrusted(targetType);
311+
return StampFactory.object(typeRef, stamp.nonNull());
312+
}
313+
}
314+
315+
AnalysisType strengthenType = strengthenGraphs.getStrengthenStampType(originalType);
316+
if (originalType.equals(strengthenType)) {
317+
/* Nothing to strengthen. */
318+
return null;
319+
}
320+
321+
Stamp newStamp;
322+
if (strengthenType == null) {
323+
/* The type and its subtypes are not instantiated. */
324+
if (stamp.nonNull()) {
325+
/* We must be in dead code. */
326+
newStamp = StampFactory.empty(JavaKind.Object);
327+
} else {
328+
newStamp = StampFactory.alwaysNull();
329+
}
330+
331+
} else {
332+
if (stamp.isExactType()) {
333+
/* We must be in dead code. */
334+
newStamp = StampFactory.empty(JavaKind.Object);
335+
} else {
336+
ResolvedJavaType targetType = toTargetFunction.apply(strengthenType);
337+
if (targetType == null) {
338+
return null;
339+
}
340+
TypeReference typeRef = TypeReference.createTrustedWithoutAssumptions(targetType);
341+
newStamp = StampFactory.object(typeRef, stamp.nonNull());
342+
}
343+
}
344+
return newStamp;
345+
}
346+
}

0 commit comments

Comments
 (0)