From 9a73d5d7e67ad800128826c71733064d59c8ca96 Mon Sep 17 00:00:00 2001 From: David Kozak Date: Wed, 10 May 2023 18:47:31 +0200 Subject: [PATCH] Introduce predicates into the points-to analysis --- .../AnalysisObjectScanningObserver.java | 11 +- .../graal/pointsto/PointsToAnalysis.java | 24 +- .../graal/pointsto/api/PointstoOptions.java | 3 + .../flow/AbstractSpecialInvokeTypeFlow.java | 5 + .../flow/AbstractStaticInvokeTypeFlow.java | 11 - .../flow/AbstractVirtualInvokeTypeFlow.java | 5 + .../flow/AllInstantiatedTypeFlow.java | 4 +- .../flow/AlwaysEnabledPredicateFlow.java | 41 + .../flow/AnyPrimitiveSourceTypeFlow.java | 31 +- .../pointsto/flow/ArrayElementsTypeFlow.java | 2 +- .../pointsto/flow/BooleanCheckTypeFlow.java | 68 ++ .../flow/BooleanInstanceOfCheckTypeFlow.java | 73 ++ .../flow/BooleanNullCheckTypeFlow.java | 57 ++ .../flow/BooleanPrimitiveCheckTypeFlow.java | 91 ++ .../graal/pointsto/flow/BoxTypeFlow.java | 2 +- .../graal/pointsto/flow/ConditionalFlow.java | 105 +++ .../flow/DynamicNewInstanceTypeFlow.java | 4 +- .../pointsto/flow/FieldFilterTypeFlow.java | 22 +- .../graal/pointsto/flow/FieldTypeFlow.java | 2 +- .../graal/pointsto/flow/FilterTypeFlow.java | 24 +- .../pointsto/flow/FormalReturnTypeFlow.java | 10 + .../graal/pointsto/flow/GlobalFlow.java | 32 + .../flow/LocalAllInstantiatedFlow.java | 47 + .../graal/pointsto/flow/LocalAnchorFlow.java | 50 ++ .../graal/pointsto/flow/MergeTypeFlow.java | 5 +- .../graal/pointsto/flow/MethodFlowsGraph.java | 6 +- .../pointsto/flow/MethodFlowsGraphClone.java | 3 +- .../graal/pointsto/flow/MethodTypeFlow.java | 2 +- .../pointsto/flow/MethodTypeFlowBuilder.java | 821 ++++++++++++++---- .../pointsto/flow/NewInstanceTypeFlow.java | 25 +- .../pointsto/flow/PredicateMergeFlow.java | 55 ++ .../pointsto/flow/PrimitiveComparison.java | 78 ++ .../flow/PrimitiveFilterTypeFlow.java | 98 +++ .../oracle/graal/pointsto/flow/TypeFlow.java | 263 +++++- .../flow/builder/TypeFlowBuilder.java | 48 +- .../flow/builder/TypeFlowGraphBuilder.java | 37 + ...BytecodeSensitiveStaticInvokeTypeFlow.java | 16 +- ...ytecodeSensitiveVirtualInvokeTypeFlow.java | 7 + .../pointsto/meta/PointsToAnalysisField.java | 1 + .../pointsto/meta/PointsToAnalysisMethod.java | 6 +- .../pointsto/meta/PointsToAnalysisType.java | 2 + .../pointsto/results/StrengthenGraphs.java | 66 +- .../typestate/AnyPrimitiveTypeState.java | 67 +- .../DefaultSpecialInvokeTypeFlow.java | 3 + .../DefaultStaticInvokeTypeFlow.java | 8 +- .../DefaultVirtualInvokeTypeFlow.java | 26 +- .../typestate/PrimitiveConstantTypeState.java | 12 +- .../typestate/PrimitiveTypeState.java | 191 ++++ .../graal/pointsto/typestate/TypeState.java | 32 +- .../pointsto/typestate/TypeStateUtils.java | 2 +- .../svm/hosted/NativeImageGenerator.java | 2 +- .../flow/SVMMethodTypeFlowBuilder.java | 10 +- 52 files changed, 2295 insertions(+), 321 deletions(-) create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AlwaysEnabledPredicateFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanCheckTypeFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanInstanceOfCheckTypeFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanNullCheckTypeFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanPrimitiveCheckTypeFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConditionalFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/GlobalFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LocalAllInstantiatedFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LocalAnchorFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PredicateMergeFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveComparison.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveFilterTypeFlow.java create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveTypeState.java diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java index 2f0abde931ee..78b5a4f97f22 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java @@ -44,14 +44,15 @@ public AnalysisObjectScanningObserver(BigBang bb) { @Override public boolean forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { - if (!field.isWritten()) { - return field.registerAsWritten(reason); - } + var changed = false; if (fieldValue.isNonNull()) { FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver); - return fieldTypeFlow.addState(getAnalysis(), TypeState.anyPrimitiveState()); + changed = fieldTypeFlow.addState(getAnalysis(), TypeState.anyPrimitiveState()); } - return false; + if (!field.isWritten()) { + changed |= field.registerAsWritten(reason); + } + return changed; } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java index 35a8a156e99f..938a2d448413 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java @@ -102,6 +102,9 @@ public abstract class PointsToAnalysis extends AbstractAnalysisEngine { * immediately represented as {@link com.oracle.graal.pointsto.flow.AnyPrimitiveSourceTypeFlow}. */ private final boolean trackPrimitiveValues; + private final AnalysisType longType; + private final AnalysisType voidType; + private final boolean usePredicates; private AnyPrimitiveSourceTypeFlow anyPrimitiveSourceTypeFlow; protected final boolean trackTypeFlowInputs; @@ -121,10 +124,15 @@ public PointsToAnalysis(OptionValues options, AnalysisUniverse universe, HostVM ClassInclusionPolicy classInclusionPolicy) { super(options, universe, hostVM, metaAccess, snippetReflectionProvider, constantReflectionProvider, wordTypes, unsupportedFeatures, debugContext, timerCollection, classInclusionPolicy); this.typeFlowTimer = timerCollection.createTimer("(typeflow)"); - this.trackPrimitiveValues = PointstoOptions.TrackPrimitiveValues.getValue(options); - this.anyPrimitiveSourceTypeFlow = new AnyPrimitiveSourceTypeFlow(null, null); this.objectType = metaAccess.lookupJavaType(Object.class); + this.longType = metaAccess.lookupJavaType(long.class); + this.voidType = metaAccess.lookupJavaType(void.class); + + this.trackPrimitiveValues = PointstoOptions.TrackPrimitiveValues.getValue(options); + this.usePredicates = PointstoOptions.UsePredicates.getValue(options); + this.anyPrimitiveSourceTypeFlow = new AnyPrimitiveSourceTypeFlow(null, longType); + this.anyPrimitiveSourceTypeFlow.enableFlow(null); /* * Make sure the all-instantiated type flow is created early. We do not have any * instantiated types yet, so the state is empty at first. @@ -270,6 +278,14 @@ public AnalysisType getObjectType() { return universe.objectType(); } + public AnalysisType getLongType() { + return longType; + } + + public AnalysisType getVoidType() { + return voidType; + } + public AnalysisType getObjectArrayType() { return metaAccess.lookupJavaType(Object[].class); } @@ -538,6 +554,10 @@ public boolean trackPrimitiveValues() { return trackPrimitiveValues; } + public boolean usePredicates() { + return usePredicates; + } + public interface TypeFlowRunnable extends DebugContextRunnable { TypeFlow getTypeFlow(); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java index d7dff7ca210a..1f21f7e0be4d 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/PointstoOptions.java @@ -39,6 +39,9 @@ public class PointstoOptions { @Option(help = "Track primitive values using the infrastructure of points-to analysis.")// public static final OptionKey TrackPrimitiveValues = new OptionKey<>(false); + @Option(help = "Use predicates in points-to analysis.")// + public static final OptionKey UsePredicates = new OptionKey<>(false); + @Option(help = "Use experimental Reachability Analysis instead of points-to.")// public static final OptionKey UseExperimentalReachabilityAnalysis = new OptionKey<>(false); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractSpecialInvokeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractSpecialInvokeTypeFlow.java index e4a987ed9cf4..a482cd97dc34 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractSpecialInvokeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractSpecialInvokeTypeFlow.java @@ -45,6 +45,11 @@ protected AbstractSpecialInvokeTypeFlow(PointsToAnalysis bb, MethodFlowsGraph me super(bb, methodFlows, original); } + @Override + protected void onFlowEnabled(PointsToAnalysis bb) { + bb.postTask(() -> onObservedUpdate(bb)); + } + @Override public boolean addState(PointsToAnalysis bb, TypeState add, boolean postFlow) { throw AnalysisError.shouldNotReachHere("The SpecialInvokeTypeFlow should not be updated directly."); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractStaticInvokeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractStaticInvokeTypeFlow.java index b990e0752dcf..0198e99e379e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractStaticInvokeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractStaticInvokeTypeFlow.java @@ -41,17 +41,6 @@ protected AbstractStaticInvokeTypeFlow(PointsToAnalysis bb, MethodFlowsGraph met super(bb, methodFlows, original); } - @Override - public void initFlow(PointsToAnalysis bb) { - /* Trigger the update for static invokes, there is no receiver to trigger it. */ - bb.postFlow(this); - } - - @Override - public boolean needsInitialization() { - return true; - } - @Override public String toString() { return "StaticInvoke<" + targetMethod.format("%h.%n") + ">" + ":" + getState(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractVirtualInvokeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractVirtualInvokeTypeFlow.java index 22d4539bd387..fc2cb080e39f 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractVirtualInvokeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AbstractVirtualInvokeTypeFlow.java @@ -87,6 +87,11 @@ public final boolean isDirectInvoke() { return false; } + @Override + protected void onFlowEnabled(PointsToAnalysis bb) { + bb.postTask(() -> onObservedUpdate(bb)); + } + @Override public boolean addState(PointsToAnalysis bb, TypeState add, boolean postFlow) { throw AnalysisError.shouldNotReachHere("The VirtualInvokeTypeFlow should not be updated directly."); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AllInstantiatedTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AllInstantiatedTypeFlow.java index a927d8cf1326..c46288794c73 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AllInstantiatedTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AllInstantiatedTypeFlow.java @@ -32,11 +32,11 @@ * of the all assignable types. This flow is used for uses that need to be notified when a sub-type * of a specific type is marked as instantiated, e.g., a saturated field access type flow needs to * be notified when a sub-type of its declared type is marked as instantiated. - * + *

* Note this flow should only be instantiated within AnalysisType. When needed, this flow should be * retrieved via calling {@link AnalysisType#getTypeFlow}. */ -public final class AllInstantiatedTypeFlow extends TypeFlow { +public final class AllInstantiatedTypeFlow extends TypeFlow implements GlobalFlow { public AllInstantiatedTypeFlow(AnalysisType declaredType, boolean canBeNull) { super(declaredType, declaredType, canBeNull); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AlwaysEnabledPredicateFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AlwaysEnabledPredicateFlow.java new file mode 100644 index 000000000000..ecae18c7e21e --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AlwaysEnabledPredicateFlow.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.typestate.TypeState; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * Always-enabled global flow that is used as a predicate before any other suitable flow is + * available. + */ +public class AlwaysEnabledPredicateFlow extends TypeFlow implements GlobalFlow { + + public AlwaysEnabledPredicateFlow() { + /* We use any primitive state here just to make sure the state is always non-empty. */ + super(TypeState.anyPrimitiveState()); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AnyPrimitiveSourceTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AnyPrimitiveSourceTypeFlow.java index fb4a6ca856f4..61b0af69e134 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AnyPrimitiveSourceTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AnyPrimitiveSourceTypeFlow.java @@ -24,6 +24,7 @@ */ package com.oracle.graal.pointsto.flow; +import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.typestate.TypeState; @@ -31,11 +32,39 @@ /** * Produces AnyPrimitive state that leads to immediate saturation of all uses. Used to represent any - * operation that is not modeled by the analysis. + * operation on primitives that is not explicitly modeled by the analysis. + *

+ * This flow can be either global (source == null) or local (source != null). */ public final class AnyPrimitiveSourceTypeFlow extends TypeFlow implements PrimitiveFlow { public AnyPrimitiveSourceTypeFlow(BytecodePosition source, AnalysisType type) { super(source, type, TypeState.anyPrimitiveState()); } + + private AnyPrimitiveSourceTypeFlow(MethodFlowsGraph methodFlows, AnyPrimitiveSourceTypeFlow original) { + super(original, methodFlows, TypeState.anyPrimitiveState()); + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + assert isLocal() : "Global flow should never be cloned: " + this; + return new AnyPrimitiveSourceTypeFlow(methodFlows, this); + } + + private boolean isLocal() { + return source != null; + } + + @Override + public boolean canSaturate() { + /* + * AnyPrimitiveSourceTypeFlow can be used as a global flow that should always propagate + * values. The global version can be identified be having source == null, and it should + * never saturate. + * + * The local versions of this flow have a concrete bytecode position and can saturate. + */ + return isLocal(); + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ArrayElementsTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ArrayElementsTypeFlow.java index 56a08d802285..1ab883d65253 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ArrayElementsTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ArrayElementsTypeFlow.java @@ -34,7 +34,7 @@ /** * This class is used to model the elements type flow for array objects. */ -public class ArrayElementsTypeFlow extends TypeFlow { +public class ArrayElementsTypeFlow extends TypeFlow implements GlobalFlow { /** The array object. */ private final AnalysisObject object; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanCheckTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanCheckTypeFlow.java new file mode 100644 index 000000000000..e6a0ee790779 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanCheckTypeFlow.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.typestate.TypeState; +import com.oracle.graal.pointsto.util.AnalysisError; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * This flow represents a boolean check, i.e. a null check, type check, or primitive check that + * converts its inputs into boolean values. Instances of this class are used as conditions for + * {@link ConditionalFlow}. + */ +public abstract class BooleanCheckTypeFlow extends TypeFlow { + + public BooleanCheckTypeFlow(BytecodePosition position, AnalysisType declaredType) { + super(position, declaredType); + } + + protected BooleanCheckTypeFlow(BooleanCheckTypeFlow original, MethodFlowsGraph methodFlows) { + super(original, methodFlows); + } + + protected static TypeState convertToBoolean(boolean canBeTrue, boolean canBeFalse) { + if (canBeTrue && canBeFalse) { + return TypeState.anyPrimitiveState(); + } else if (canBeTrue) { + return TypeState.forBoolean(true); + } else if (canBeFalse) { + return TypeState.forBoolean(false); + } + return TypeState.forEmpty(); + } + + protected static TypeState convertToBoolean(TypeState trueState, TypeState falseState) { + return convertToBoolean(trueState.isNotEmpty(), falseState.isNotEmpty()); + } + + @Override + public void addPredicated(PointsToAnalysis bb, TypeFlow predicatedFlow) { + AnalysisError.shouldNotReachHere(getClass() + " shouldn't act as a predicate."); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanInstanceOfCheckTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanInstanceOfCheckTypeFlow.java new file mode 100644 index 000000000000..0db031d0fb02 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanInstanceOfCheckTypeFlow.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.typestate.TypeState; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * This flow represents a type check used by {@link ConditionalFlow}. + */ +public class BooleanInstanceOfCheckTypeFlow extends BooleanCheckTypeFlow { + private final AnalysisType checkedType; + private final boolean isExact; + private final boolean includeNull; + + public BooleanInstanceOfCheckTypeFlow(BytecodePosition position, AnalysisType checkedType, boolean isExact, boolean includeNull, AnalysisType longType) { + super(position, longType); + this.checkedType = checkedType; + this.isExact = isExact; + this.includeNull = includeNull; + } + + private BooleanInstanceOfCheckTypeFlow(MethodFlowsGraph methodFlows, BooleanInstanceOfCheckTypeFlow original) { + super(original, methodFlows); + this.checkedType = original.checkedType; + this.isExact = original.isExact; + this.includeNull = original.includeNull; + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new BooleanInstanceOfCheckTypeFlow(methodFlows, this); + } + + @Override + public TypeState filter(PointsToAnalysis bb, TypeState update) { + TypeState canBeTrue; + TypeState canBeFalse; + if (isExact) { + canBeTrue = TypeState.forIntersection(bb, update, TypeState.forExactType(bb, checkedType, includeNull)); + canBeFalse = TypeState.forSubtraction(bb, update, TypeState.forExactType(bb, checkedType, includeNull)); + } else { + canBeTrue = TypeState.forIntersection(bb, update, checkedType.getAssignableTypes(includeNull)); + canBeFalse = TypeState.forSubtraction(bb, update, checkedType.getAssignableTypes(includeNull)); + } + return convertToBoolean(canBeTrue, canBeFalse); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanNullCheckTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanNullCheckTypeFlow.java new file mode 100644 index 000000000000..cda0e675cac2 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanNullCheckTypeFlow.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.typestate.TypeState; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * This flow represents a null check used by {@link ConditionalFlow}. + */ +public class BooleanNullCheckTypeFlow extends BooleanCheckTypeFlow { + + public BooleanNullCheckTypeFlow(BytecodePosition source, AnalysisType inputType) { + super(source, inputType); + } + + private BooleanNullCheckTypeFlow(MethodFlowsGraph methodFlows, BooleanNullCheckTypeFlow original) { + super(original, methodFlows); + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new BooleanNullCheckTypeFlow(methodFlows, this); + } + + @Override + public TypeState filter(PointsToAnalysis bb, TypeState newState) { + var hasNull = newState.canBeNull(); + var hasTypes = newState.typesCount() > 0; + return convertToBoolean(hasNull, hasTypes); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanPrimitiveCheckTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanPrimitiveCheckTypeFlow.java new file mode 100644 index 000000000000..92327796d25d --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BooleanPrimitiveCheckTypeFlow.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.typestate.TypeState; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * This flow represents a primitive comparison using one of the {@link PrimitiveComparison} + * operators. This flow is used by {@link ConditionalFlow}. + */ +public class BooleanPrimitiveCheckTypeFlow extends BooleanCheckTypeFlow { + + private final TypeFlow left; + private final TypeFlow right; + private final PrimitiveComparison comparison; + + public BooleanPrimitiveCheckTypeFlow(BytecodePosition position, AnalysisType declaredType, TypeFlow left, TypeFlow right, PrimitiveComparison comparison) { + super(position, declaredType); + this.left = left; + this.right = right; + this.comparison = comparison; + } + + private BooleanPrimitiveCheckTypeFlow(PointsToAnalysis bb, MethodFlowsGraph methodFlows, BooleanPrimitiveCheckTypeFlow original) { + super(original, methodFlows); + this.left = methodFlows.lookupCloneOf(bb, original.left); + this.right = methodFlows.lookupCloneOf(bb, original.right); + this.comparison = original.comparison; + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new BooleanPrimitiveCheckTypeFlow(bb, methodFlows, this); + } + + @Override + public boolean addState(PointsToAnalysis bb, TypeState add) { + return super.addState(bb, eval()); + } + + @Override + protected void onInputSaturated(PointsToAnalysis bb, TypeFlow input) { + /* + * If an input saturated, it does not mean that the condition has to always saturate as + * well, e.g. Any == {5} will return {5}. + */ + super.addState(bb, eval()); + } + + /** + * Compares the type states of left and right. + * + * @return can be either empty, true, false, or any. + */ + public TypeState eval() { + var leftState = left.isSaturated() ? TypeState.anyPrimitiveState() : left.getState(); + var rightState = right.isSaturated() ? TypeState.anyPrimitiveState() : right.getState(); + if (leftState.isEmpty() || rightState.isEmpty()) { + return TypeState.forEmpty(); + } + assert leftState.isPrimitive() : left; + assert rightState.isPrimitive() : right; + return convertToBoolean(TypeState.filter(leftState, comparison, rightState), TypeState.filter(leftState, comparison.negate(), rightState)); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BoxTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BoxTypeFlow.java index 369475a34305..872c7ab3f474 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BoxTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/BoxTypeFlow.java @@ -32,7 +32,7 @@ public class BoxTypeFlow extends NewInstanceTypeFlow { public BoxTypeFlow(BytecodePosition position, AnalysisType type) { - super(position, type); + super(position, type, true); } public BoxTypeFlow(PointsToAnalysis bb, BoxTypeFlow original, MethodFlowsGraph methodFlows) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConditionalFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConditionalFlow.java new file mode 100644 index 000000000000..2c541020ccff --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ConditionalFlow.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.typestate.PrimitiveTypeState; +import com.oracle.graal.pointsto.typestate.TypeState; +import com.oracle.graal.pointsto.util.AnalysisError; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * This flow represents a ternary operator. The type state is computed based on the type state of + * the condition and the left and right inputs. + *

+ * The condition used by conditional flow should always produce a primitive or empty type state. + *

+ * The conditional flow is connected via observer edge with the condition and via use edges with + * true and false values, but this decision is quite arbitrary, its implementation can check the + * values of all its three inputs anyways. + */ +public class ConditionalFlow extends TypeFlow { + + private final TypeFlow condition; + private final TypeFlow trueValue; + private final TypeFlow falseValue; + + public ConditionalFlow(BytecodePosition source, AnalysisType declaredType, TypeFlow condition, TypeFlow trueValue, TypeFlow falseValue) { + super(source, declaredType); + assert condition.isPrimitiveFlow() : condition; + this.condition = condition; + this.trueValue = trueValue; + this.falseValue = falseValue; + } + + private ConditionalFlow(PointsToAnalysis bb, MethodFlowsGraph methodFlows, ConditionalFlow original) { + super(original, methodFlows); + this.condition = methodFlows.lookupCloneOf(bb, original.condition); + this.trueValue = methodFlows.lookupCloneOf(bb, original.trueValue); + this.falseValue = methodFlows.lookupCloneOf(bb, original.falseValue); + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new ConditionalFlow(bb, methodFlows, this); + } + + @Override + public void onObservedUpdate(PointsToAnalysis bb) { + addState(bb, condition.getState()); + } + + @Override + public void onObservedSaturated(PointsToAnalysis bb, TypeFlow observed) { + /* If the condition is already saturated, merge both inputs. */ + super.addState(bb, TypeState.forUnion(bb, trueValue.getState(), falseValue.getState())); + } + + @Override + public boolean addState(PointsToAnalysis bb, TypeState add) { + if (condition.isSaturated()) { + /* If the condition is already saturated, merge both inputs. */ + return super.addState(bb, TypeState.forUnion(bb, trueValue.getState(), falseValue.getState())); + } + var conditionValue = condition.getState(); + if (conditionValue.isEmpty()) { + /* If the condition is empty, do not produce any output yet. */ + return false; + } + if (conditionValue instanceof PrimitiveTypeState prim) { + var canBeTrue = prim.canBeTrue(); + var canBeFalse = prim.canBeFalse(); + if (canBeTrue && !canBeFalse) { + return super.addState(bb, trueValue.getState()); + } else if (!canBeTrue && canBeFalse) { + return super.addState(bb, falseValue.getState()); + } + return super.addState(bb, TypeState.forUnion(bb, trueValue.getState(), falseValue.getState())); + } + throw AnalysisError.shouldNotReachHere("Unexpected non-primitive type state of the condition: " + conditionValue + ", at flow " + this); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/DynamicNewInstanceTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/DynamicNewInstanceTypeFlow.java index e17cc05b1632..63df237c007b 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/DynamicNewInstanceTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/DynamicNewInstanceTypeFlow.java @@ -67,7 +67,9 @@ public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph met @Override public void initFlow(PointsToAnalysis bb) { - this.newTypeFlow.addObserver(bb, this); + assert !bb.usePredicates() || newTypeFlow.getPredicate() != null || MethodFlowsGraph.nonMethodFlow(newTypeFlow) : "Missing predicate for the flow " + newTypeFlow + ", which is input for " + + this; + newTypeFlow.addObserver(bb, this); } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldFilterTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldFilterTypeFlow.java index 8ea933c85ea6..cd217ed7bd4c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldFilterTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldFilterTypeFlow.java @@ -38,7 +38,7 @@ * and it 'includes-null', i.e., it allows null values to pass through. However, it's 'source' is an * AnalysisField and not a ValueNode, thus it's a completely different class. */ -public class FieldFilterTypeFlow extends TypeFlow { +public class FieldFilterTypeFlow extends TypeFlow implements GlobalFlow { public FieldFilterTypeFlow(AnalysisField field) { super(field, field.getType()); @@ -64,9 +64,27 @@ public TypeState filter(PointsToAnalysis bb, TypeState update) { } } + @Override + public void addPredicated(PointsToAnalysis bb, TypeFlow predicatedFlow) { + if (isSaturated()) { + declaredType.getTypeFlow(bb, true).addPredicated(bb, predicatedFlow); + return; + } + super.addPredicated(bb, predicatedFlow); + } + @Override protected void onInputSaturated(PointsToAnalysis bb, TypeFlow input) { - setSaturated(); + if (!isFlowEnabled()) { + inputSaturated = true; + /* Another thread could enable the flow in the meantime, so check again. */ + if (!isFlowEnabled()) { + return; + } + } + if (!setSaturated()) { + return; + } /* Swap out this flow with its declared type flow. */ swapOut(bb, declaredType.getTypeFlow(bb, true)); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java index 168c4d483987..f56aaa40790a 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java @@ -34,7 +34,7 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.typestate.TypeState; -public class FieldTypeFlow extends TypeFlow { +public class FieldTypeFlow extends TypeFlow implements GlobalFlow { private static final AtomicReferenceFieldUpdater FILTER_FLOW_UPDATER = AtomicReferenceFieldUpdater.newUpdater(FieldTypeFlow.class, FieldFilterTypeFlow.class, "filterFlow"); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FilterTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FilterTypeFlow.java index 7bd28c7ceacd..42a720229e8f 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FilterTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FilterTypeFlow.java @@ -100,11 +100,31 @@ public TypeState filter(PointsToAnalysis bb, TypeState update) { return result; } + @Override + public void addPredicated(PointsToAnalysis bb, TypeFlow predicatedFlow) { + if (isAssignable && isSaturated()) { + filterType.getTypeFlow(bb, includeNull).addPredicated(bb, predicatedFlow); + return; + } + super.addPredicated(bb, predicatedFlow); + } + @Override protected void onInputSaturated(PointsToAnalysis bb, TypeFlow input) { if (isAssignable) { - /* Swap this flow out at its uses/observers with its filter type flow. */ - setSaturated(); + if (!isFlowEnabled()) { + inputSaturated = true; + /* Another thread could enable the flow in the meantime, so check again. */ + if (!isFlowEnabled()) { + return; + } + } + if (!setSaturated()) { + return; + } + /* + * Swap this flow out at its uses/observers/predicated flows with its filter type flow. + */ swapOut(bb, filterType.getTypeFlow(bb, includeNull)); } else { super.onInputSaturated(bb, input); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReturnTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReturnTypeFlow.java index 4dd18368860a..88ec0eb06e24 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReturnTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReturnTypeFlow.java @@ -29,6 +29,7 @@ import com.oracle.graal.pointsto.typestate.TypeState; import jdk.vm.ci.code.BytecodePosition; +import jdk.vm.ci.meta.JavaKind; public class FormalReturnTypeFlow extends TypeFlow { public FormalReturnTypeFlow(BytecodePosition source, AnalysisType declaredType) { @@ -41,6 +42,15 @@ public FormalReturnTypeFlow(FormalReturnTypeFlow original, MethodFlowsGraph meth @Override public TypeState filter(PointsToAnalysis bb, TypeState newState) { + if (declaredType.getJavaKind() == JavaKind.Void) { + /* + * Void ReturnTypeFlow has a use edge from the latest predicate, which can propagate + * random values. We only use this edge to signal that the method can return, we don't + * care about the actual value. We sanitize it to AnyPrimitive to prevent from + * primitive/object collisions in addState. + */ + return newState.isEmpty() ? TypeState.forEmpty() : TypeState.anyPrimitiveState(); + } /* * Always filter the formal return state with the declared type, even if the type flow * constraints are not relaxed. This avoids imprecision caused by MethodHandle API methods diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/GlobalFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/GlobalFlow.java new file mode 100644 index 000000000000..95e1270c2ae8 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/GlobalFlow.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +/** + * A marker interface for flows that are global, i.e. do not have a position in a specific method, + * and as a consequence they are enabled immediately upon creation. + */ +public interface GlobalFlow { +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LocalAllInstantiatedFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LocalAllInstantiatedFlow.java new file mode 100644 index 000000000000..36cdc016c2c0 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LocalAllInstantiatedFlow.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; + +/** + * A local use of AllInstantiatedFlow that can have a predicate. + */ +public class LocalAllInstantiatedFlow extends TypeFlow { + + public LocalAllInstantiatedFlow(AnalysisType declaredType) { + super(declaredType, declaredType); + } + + private LocalAllInstantiatedFlow(MethodFlowsGraph methodFlows, LocalAllInstantiatedFlow original) { + super(original, methodFlows); + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new LocalAllInstantiatedFlow(methodFlows, this); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LocalAnchorFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LocalAnchorFlow.java new file mode 100644 index 000000000000..82f0753176bc --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/LocalAnchorFlow.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * Used to prevent value propagation from the end of a branch until the latest predicate in that + * branch has a non-empty type state. + */ +public class LocalAnchorFlow extends TypeFlow { + + public LocalAnchorFlow(BytecodePosition source, AnalysisType declaredType) { + super(source, declaredType); + } + + public LocalAnchorFlow(MethodFlowsGraph methodFlows, LocalAnchorFlow original) { + super(original, methodFlows); + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new LocalAnchorFlow(methodFlows, this); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MergeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MergeTypeFlow.java index 1b0c129432c2..a735c2818890 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MergeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MergeTypeFlow.java @@ -25,13 +25,14 @@ package com.oracle.graal.pointsto.flow; import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; import jdk.vm.ci.code.BytecodePosition; public class MergeTypeFlow extends TypeFlow { - public MergeTypeFlow(BytecodePosition position) { - super(position, null); + public MergeTypeFlow(BytecodePosition position, AnalysisType declaredType) { + super(position, declaredType); } public MergeTypeFlow(MergeTypeFlow original, MethodFlowsGraph methodFlows) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraph.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraph.java index f2051b871222..e3e3819e3f2f 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraph.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraph.java @@ -120,8 +120,10 @@ public static boolean crossMethodUse(TypeFlow flow, TypeFlow use) { public static boolean nonMethodFlow(TypeFlow flow) { /* * These flows do not belong to any method, but can be reachable from a use. + * + * AnyPrimitiveFlow can be either global (source == null) or local (source != null) */ - return flow instanceof AllInstantiatedTypeFlow || flow instanceof AllSynchronizedTypeFlow || flow instanceof AnyPrimitiveSourceTypeFlow; + return flow instanceof AllInstantiatedTypeFlow || flow instanceof AllSynchronizedTypeFlow || (flow instanceof AnyPrimitiveSourceTypeFlow && flow.getSource() == null); } /** @@ -448,6 +450,7 @@ public void saturateAllParameters(PointsToAnalysis bb) { AnalysisError.guarantee(bb.isBaseLayerAnalysisEnabled()); for (TypeFlow parameter : getParameters()) { if (parameter != null && parameter.canSaturate()) { + parameter.enableFlow(bb); parameter.onSaturated(bb); } } @@ -464,6 +467,7 @@ public void saturateAllParameters(PointsToAnalysis bb) { if (miscEntryFlows != null) { for (TypeFlow miscEntryFlow : miscEntryFlows) { if (miscEntryFlow != null && miscEntryFlow.canSaturate()) { + miscEntryFlow.enableFlow(bb); miscEntryFlow.onSaturated(bb); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraphClone.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraphClone.java index e788b1f8a166..191572d8d3d2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraphClone.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodFlowsGraphClone.java @@ -127,7 +127,8 @@ public > T lookupCloneOf(PointsToAnalysis bb, T original) assert !(original instanceof FieldTypeFlow) : "Trying to clone a field type flow"; assert !(original instanceof ArrayElementsTypeFlow) : "Trying to clone an mixed elements type flow"; - if (original instanceof AllInstantiatedTypeFlow || original instanceof AllSynchronizedTypeFlow || original instanceof AnyPrimitiveSourceTypeFlow) { + if (original instanceof AllInstantiatedTypeFlow || original instanceof AllSynchronizedTypeFlow || (original instanceof AnyPrimitiveSourceTypeFlow && original.getSource() == null) || + original instanceof ConstantPrimitiveSourceTypeFlow) { /* These flows are not cloneable. */ return original; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java index 04d5a0a5d63d..255cf7887e0d 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlow.java @@ -125,7 +125,7 @@ public MethodFlowsGraphInfo getMethodFlowsGraphInfo() { public MethodFlowsGraph getMethodFlowsGraph() { ensureFlowsGraphSealed(); - assert flowsGraph != null; + assert flowsGraph != null : "Flows graph not available yet."; return flowsGraph; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java index a1bcbe567a84..4b4089a54d53 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java @@ -27,6 +27,7 @@ import static jdk.vm.ci.common.JVMCIError.shouldNotReachHere; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -61,6 +62,7 @@ import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.svm.common.meta.MultiMethod; +import jdk.graal.compiler.core.common.SuppressFBWarnings; import jdk.graal.compiler.core.common.spi.ForeignCallDescriptor; import jdk.graal.compiler.core.common.spi.ForeignCallsProvider; import jdk.graal.compiler.core.common.type.AbstractObjectStamp; @@ -68,15 +70,16 @@ import jdk.graal.compiler.core.common.type.ObjectStamp; import jdk.graal.compiler.core.common.type.Stamp; import jdk.graal.compiler.core.common.type.TypeReference; +import jdk.graal.compiler.core.common.type.VoidStamp; import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.graph.Node.NodeIntrinsic; import jdk.graal.compiler.graph.NodeBitMap; import jdk.graal.compiler.graph.NodeInputList; import jdk.graal.compiler.graph.NodeSourcePosition; +import jdk.graal.compiler.nodes.AbstractBeginNode; import jdk.graal.compiler.nodes.AbstractEndNode; import jdk.graal.compiler.nodes.AbstractMergeNode; -import jdk.graal.compiler.nodes.BeginNode; import jdk.graal.compiler.nodes.CallTargetNode.InvokeKind; import jdk.graal.compiler.nodes.ConstantNode; import jdk.graal.compiler.nodes.EndNode; @@ -95,15 +98,28 @@ import jdk.graal.compiler.nodes.ParameterNode; import jdk.graal.compiler.nodes.PhiNode; import jdk.graal.compiler.nodes.ReturnNode; +import jdk.graal.compiler.nodes.ShortCircuitOrNode; import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.UnaryOpLogicNode; import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.ValuePhiNode; +import jdk.graal.compiler.nodes.calc.CompareNode; +import jdk.graal.compiler.nodes.calc.ConditionalNode; +import jdk.graal.compiler.nodes.calc.FloatEqualsNode; +import jdk.graal.compiler.nodes.calc.FloatLessThanNode; +import jdk.graal.compiler.nodes.calc.IntegerEqualsNode; +import jdk.graal.compiler.nodes.calc.IntegerLowerThanNode; +import jdk.graal.compiler.nodes.calc.IntegerTestNode; import jdk.graal.compiler.nodes.calc.IsNullNode; +import jdk.graal.compiler.nodes.calc.ObjectEqualsNode; import jdk.graal.compiler.nodes.extended.BoxNode; import jdk.graal.compiler.nodes.extended.BytecodeExceptionNode; import jdk.graal.compiler.nodes.extended.BytecodeExceptionNode.BytecodeExceptionKind; +import jdk.graal.compiler.nodes.extended.ClassIsArrayNode; import jdk.graal.compiler.nodes.extended.FieldOffsetProvider; import jdk.graal.compiler.nodes.extended.ForeignCall; import jdk.graal.compiler.nodes.extended.GetClassNode; +import jdk.graal.compiler.nodes.extended.ObjectIsArrayNode; import jdk.graal.compiler.nodes.extended.RawLoadNode; import jdk.graal.compiler.nodes.extended.RawStoreNode; import jdk.graal.compiler.nodes.java.AtomicReadAndAddNode; @@ -112,6 +128,7 @@ import jdk.graal.compiler.nodes.java.DynamicNewArrayNode; import jdk.graal.compiler.nodes.java.DynamicNewInstanceNode; import jdk.graal.compiler.nodes.java.ExceptionObjectNode; +import jdk.graal.compiler.nodes.java.InstanceOfDynamicNode; import jdk.graal.compiler.nodes.java.InstanceOfNode; import jdk.graal.compiler.nodes.java.LoadFieldNode; import jdk.graal.compiler.nodes.java.LoadIndexedNode; @@ -152,6 +169,12 @@ import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.VMConstant; +/** + * This class creates the type flow graph for a given method, which is done via a reverse post-order + * traversal of its IR. The state kept during the traversal is mostly contained within + * {@link TypeFlowsOfNodes} and its predicate-specific subclass + * {@link TypeFlowsOfNodesWithPredicates}. + */ public class MethodTypeFlowBuilder { protected final PointsToAnalysis bb; @@ -167,13 +190,30 @@ public class MethodTypeFlowBuilder { protected final TypeFlowGraphBuilder typeFlowGraphBuilder; protected List> postInitFlows = List.of(); + /** + * Always enabled predicate builder used when no other flow is available as a predicate yet. + */ + protected final TypeFlowBuilder alwaysEnabled; + /** + * Any primitive source type flow builder used for modelling unsupported primitive operations + * when no predicate better than alwaysEnabled is available, so it is not worth to make a + * 'local' flow for that. + */ private final TypeFlowBuilder anyPrimitiveSourceTypeFlowBuilder; public MethodTypeFlowBuilder(PointsToAnalysis bb, PointsToAnalysisMethod method, MethodFlowsGraph flowsGraph, GraphKind graphKind) { this.bb = bb; this.method = method; this.graphKind = graphKind; - this.anyPrimitiveSourceTypeFlowBuilder = bb.trackPrimitiveValues() ? TypeFlowBuilder.create(bb, null, AnyPrimitiveSourceTypeFlow.class, bb::getAnyPrimitiveSourceTypeFlow) : null; + if (bb.trackPrimitiveValues()) { + this.alwaysEnabled = bb.usePredicates() + ? TypeFlowBuilder.create(bb, method, null, PointsToAnalysis.syntheticSourcePosition(method), AlwaysEnabledPredicateFlow.class, AlwaysEnabledPredicateFlow::new) + : null; + this.anyPrimitiveSourceTypeFlowBuilder = TypeFlowBuilder.create(bb, method, alwaysEnabled, null, AnyPrimitiveSourceTypeFlow.class, bb::getAnyPrimitiveSourceTypeFlow); + } else { + this.alwaysEnabled = null; + this.anyPrimitiveSourceTypeFlowBuilder = null; + } if (flowsGraph == null) { this.flowsGraph = new MethodFlowsGraph(method, graphKind); newFlowsGraph = true; @@ -228,7 +268,7 @@ private boolean parse(Object reason, boolean forceReparse) { } // Do it again after canonicalization changed type checks and field accesses. - registerUsedElements(bb, graph); + registerUsedElements(bb, graph, bb.usePredicates()); return true; } catch (Throwable ex) { @@ -236,7 +276,7 @@ private boolean parse(Object reason, boolean forceReparse) { } } - public static void registerUsedElements(PointsToAnalysis bb, StructuredGraph graph) { + public static void registerUsedElements(PointsToAnalysis bb, StructuredGraph graph, boolean usePredicates) { PointsToAnalysisMethod method = (PointsToAnalysisMethod) graph.method(); HostedProviders providers = bb.getProviders(method); for (Node n : graph.getNodes()) { @@ -250,10 +290,12 @@ public static void registerUsedElements(PointsToAnalysis bb, StructuredGraph gra } else if (n instanceof NewInstanceNode) { NewInstanceNode node = (NewInstanceNode) n; AnalysisType type = (AnalysisType) node.instanceClass(); - type.registerAsInstantiated(AbstractAnalysisEngine.sourcePosition(node)); - for (var f : type.getInstanceFields(true)) { - var field = (AnalysisField) f; - field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(field.getStorageKind())); + if (!usePredicates) { + type.registerAsInstantiated(AbstractAnalysisEngine.sourcePosition(node)); + for (var f : type.getInstanceFields(true)) { + var field = (AnalysisField) f; + field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(field.getStorageKind())); + } } } else if (n instanceof NewInstanceWithExceptionNode) { @@ -307,8 +349,8 @@ public static void registerUsedElements(PointsToAnalysis bb, StructuredGraph gra AnalysisField field = (AnalysisField) node.field(); field.registerAsWritten(AbstractAnalysisEngine.sourcePosition(node)); - } else if (n instanceof ConstantNode) { - ConstantNode cn = (ConstantNode) n; + } else if (n instanceof ConstantNode cn) { + /* GR-58472: We should try to delay marking constants as instantiated */ JavaConstant root = cn.asJavaConstant(); if (cn.hasUsages() && cn.isJavaConstant() && root.getJavaKind() == JavaKind.Object && root.isNonNull()) { assert StampTool.isExactType(cn) : cn; @@ -458,6 +500,7 @@ private boolean handleNodeIntrinsic() { BytecodePosition source = new BytecodePosition(null, method, 0); returnTypeFlow = bb.analysisPolicy().proxy(source, returnTypeFlow); FormalReturnTypeFlow resultFlow = new FormalReturnTypeFlow(source, returnType); + resultFlow.enableFlow(bb); bb.analysisPolicy().addOriginalUse(bb, returnTypeFlow, resultFlow); flowsGraph.addMiscEntryFlow(returnTypeFlow); flowsGraph.setReturnFlow(resultFlow); @@ -479,6 +522,11 @@ private void handleOpaqueReturn() { BytecodePosition position = AbstractAnalysisEngine.syntheticSourcePosition(null, method); var returnFlow = new FormalReturnTypeFlow(position, returnType); + /* + * If we cannot compute the return, we also probably cannot determine its reachability, e.g. + * in the case of ContinuationInternals.{enterSpecial1,doYield1}. + */ + returnFlow.enableFlow(bb); flowsGraph.setReturnFlow(returnFlow); assert returnType.equals(returnFlow.getDeclaredType()) : returnType + " != " + returnFlow.getDeclaredType(); @@ -509,7 +557,11 @@ private void insertPlaceholderParamAndReturnFlows() { if (flowsGraph.getReturnFlow() == null) { AnalysisType returnType = method.getSignature().getReturnType(); - if (bb.isSupportedJavaKind(returnType.getJavaKind())) { + if (bb.isSupportedJavaKind(returnType.getJavaKind()) || (bb.usePredicates() && returnType.getJavaKind() == JavaKind.Void)) { + /* + * We want to determine whether void methods can return, so we need to create + * FormalReturnTypeFlow for them. + */ flowsGraph.setReturnFlow(new FormalReturnTypeFlow(position, returnType)); } } @@ -519,12 +571,13 @@ private void insertPlaceholderParamAndReturnFlows() { private void createTypeFlow() { processedNodes = new NodeBitMap(graph); - TypeFlowsOfNodes typeFlows = new TypeFlowsOfNodes(); + var typeFlows = bb.usePredicates() ? new TypeFlowsOfNodesWithPredicates() : new TypeFlowsOfNodes(); + typeFlows.setPredicate(alwaysEnabled); for (Node n : graph.getNodes()) { if (n instanceof ParameterNode) { ParameterNode node = (ParameterNode) n; if (bb.isSupportedJavaKind(node.getStackKind())) { - TypeFlowBuilder paramBuilder = TypeFlowBuilder.create(bb, node, FormalParamTypeFlow.class, () -> { + TypeFlowBuilder paramBuilder = TypeFlowBuilder.create(bb, method, typeFlows.getPredicate(), node, FormalParamTypeFlow.class, () -> { boolean isStatic = Modifier.isStatic(method.getModifiers()); int index = node.index(); FormalParamTypeFlow parameter = flowsGraph.getParameter(index); @@ -549,11 +602,11 @@ private void createTypeFlow() { typeFlows.add(node, paramBuilder); typeFlowGraphBuilder.registerSinkBuilder(paramBuilder); } - } else if (n instanceof BoxNode) { + } else if (n instanceof BoxNode && !typeFlows.usePredicates()) { BoxNode node = (BoxNode) n; AnalysisType type = (AnalysisType) StampTool.typeOrNull(node, bb.getMetaAccess()); - TypeFlowBuilder boxBuilder = TypeFlowBuilder.create(bb, node, BoxTypeFlow.class, () -> { + TypeFlowBuilder boxBuilder = TypeFlowBuilder.create(bb, method, typeFlows.getPredicate(), node, BoxTypeFlow.class, () -> { BoxTypeFlow boxFlow = new BoxTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type); flowsGraph.addMiscEntryFlow(boxFlow); return boxFlow; @@ -563,8 +616,8 @@ private void createTypeFlow() { for (Node input : n.inputs()) { /* - * TODO change the handling of constants so that the SourceTypeFlow is created on - * demand, with the optimization that only one SourceTypeFlow is created ever for + * GR-58474: Change the handling of constants so that the SourceTypeFlow is created + * on demand, with the optimization that only one SourceTypeFlow is created ever for * every distinct object (using, e.g., caching in a global IdentityHashMap). */ if (input instanceof ConstantNode && !typeFlows.contains((ConstantNode) input)) { @@ -573,25 +626,29 @@ private void createTypeFlow() { if (node.asJavaConstant() == null && (constant instanceof VMConstant || constant instanceof CStringConstant)) { // do nothing } else if (node.asJavaConstant().isNull()) { - TypeFlowBuilder sourceBuilder = TypeFlowBuilder.create(bb, node, ConstantTypeFlow.class, () -> { - ConstantTypeFlow constantSource = new ConstantTypeFlow(AbstractAnalysisEngine.sourcePosition(node), null, TypeState.forNull()); - flowsGraph.addMiscEntryFlow(constantSource); - return constantSource; - }); - typeFlows.add(node, sourceBuilder); + if (!typeFlows.usePredicates()) { + TypeFlowBuilder sourceBuilder = TypeFlowBuilder.create(bb, method, typeFlows.getPredicate(), node, ConstantTypeFlow.class, () -> { + ConstantTypeFlow constantSource = new ConstantTypeFlow(AbstractAnalysisEngine.sourcePosition(node), null, TypeState.forNull()); + flowsGraph.addMiscEntryFlow(constantSource); + return constantSource; + }); + typeFlows.add(node, sourceBuilder); + } } else if (node.asJavaConstant().getJavaKind() == JavaKind.Object) { - assert StampTool.isExactType(node) : node; - TypeFlowBuilder sourceBuilder = TypeFlowBuilder.create(bb, node, ConstantTypeFlow.class, () -> { - AnalysisType type = (AnalysisType) StampTool.typeOrNull(node, bb.getMetaAccess()); - assert type.isInstantiated() : type; - JavaConstant constantValue = node.asJavaConstant(); - BytecodePosition position = AbstractAnalysisEngine.sourcePosition(node); - JavaConstant heapConstant = bb.getUniverse().getHeapScanner().toImageHeapObject(constantValue, new EmbeddedRootScan(position, constantValue)); - ConstantTypeFlow constantSource = new ConstantTypeFlow(position, type, TypeState.forConstant(this.bb, heapConstant, type)); - flowsGraph.addMiscEntryFlow(constantSource); - return constantSource; - }); - typeFlows.add(node, sourceBuilder); + if (!typeFlows.usePredicates()) { + assert StampTool.isExactType(node) : node; + TypeFlowBuilder sourceBuilder = TypeFlowBuilder.create(bb, method, typeFlows.getPredicate(), node, ConstantTypeFlow.class, () -> { + AnalysisType type = (AnalysisType) StampTool.typeOrNull(node, bb.getMetaAccess()); + assert type.isInstantiated() : type; + JavaConstant constantValue = node.asJavaConstant(); + BytecodePosition position = AbstractAnalysisEngine.sourcePosition(node); + JavaConstant heapConstant = bb.getUniverse().getHeapScanner().toImageHeapObject(constantValue, new EmbeddedRootScan(position, constantValue)); + ConstantTypeFlow constantSource = new ConstantTypeFlow(position, type, TypeState.forConstant(this.bb, heapConstant, type)); + flowsGraph.addMiscEntryFlow(constantSource); + return constantSource; + }); + typeFlows.add(node, sourceBuilder); + } } } } @@ -627,12 +684,9 @@ protected void apply(boolean forceReparse, Object reason) { * We don't need to analyze this method. We already know its return type state from the * open world analysis. We just install a return flow to link it with its uses. */ - AnalysisType returnType = method.getSignature().getReturnType(); - if (returnType.getJavaKind().isObject()) { - // GR-52421: the return should not be opaque, it should be the - // persisted result of the open-world analysis - handleOpaqueReturn(); - } + // GR-52421: the return should not be opaque, it should be the + // persisted result of the open-world analysis + handleOpaqueReturn(); // GR-52421: verify that tracked parameter state is subset of persisted state insertPlaceholderParamAndReturnFlows(); return; @@ -670,12 +724,29 @@ protected void apply(boolean forceReparse, Object reason) { method.setAnalyzedGraph(GraphEncoder.encodeSingleGraph(graph, AnalysisParsedGraph.HOST_ARCHITECTURE, flowsGraph.getNodeFlows().getKeys())); } + /** + * It only makes sense to create a local version of all instantiated if it will be guarded by a + * predicate more precise than alwaysEnabled. + */ + protected TypeFlow maybePatchAllInstantiated(TypeFlow flow, AnalysisType declaredType, Object predicate) { + if (bb.usePredicates() && flow instanceof AllInstantiatedTypeFlow && predicate != alwaysEnabled) { + var localFlow = new LocalAllInstantiatedFlow(declaredType); + flowsGraph.addMiscEntryFlow(localFlow); + flow.addUse(bb, localFlow); + return localFlow; + } + return flow; + } + /** * Fixed point analysis state. It stores the type flows for all nodes of the method's graph. */ protected class TypeFlowsOfNodes extends MergeableState implements Cloneable { - private final Map> flows; + /** + * Used to establish dependencies between flows. + */ + protected final Map> flows; TypeFlowsOfNodes() { this.flows = new HashMap<>(); @@ -705,57 +776,76 @@ public TypeFlowBuilder lookup(ValueNode n) { TypeFlowBuilder result = flows.get(node); if (result == null) { /* - * There is no type flow set, yet. Therefore we have no info for the node. + * There is no type flow set, yet. Therefore, we have no info for the node. */ Stamp s = n.stamp(NodeView.DEFAULT); if (s instanceof IntegerStamp stamp) { - long lo = stamp.lowerBound(); - long hi = stamp.upperBound(); - var type = (AnalysisType) stamp.javaType(bb.getMetaAccess()); - if (lo == hi) { - result = TypeFlowBuilder.create(bb, node, ConstantPrimitiveSourceTypeFlow.class, () -> { - var flow = new ConstantPrimitiveSourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, lo); - flowsGraph.addMiscEntryFlow(flow); - return flow; - }); - } else { - result = anyPrimitiveSourceTypeFlowBuilder; - } + result = handleIntegerStamp(stamp, node); } else if (s instanceof ObjectStamp stamp) { - if (stamp.isEmpty()) { - throw AnalysisError.shouldNotReachHere("Stamp for node " + n + " is empty."); - } - AnalysisType stampType = (AnalysisType) StampTool.typeOrNull(stamp, bb.getMetaAccess()); - if (stamp.isExactType()) { - /* - * We are lucky: the stamp tells us which type the node has. - */ - result = TypeFlowBuilder.create(bb, node, SourceTypeFlow.class, () -> { - SourceTypeFlow src = new SourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), stampType, !stamp.nonNull()); - flowsGraph.addMiscEntryFlow(src); - return src; - }); - - } else { - /* - * Use a type state which consists of all allocated types (which are - * compatible to the node's type). This is a conservative assumption. - */ - result = TypeFlowBuilder.create(bb, node, TypeFlow.class, () -> { - TypeFlow proxy = bb.analysisPolicy().proxy(AbstractAnalysisEngine.sourcePosition(node), stampType.getTypeFlow(bb, true)); - flowsGraph.addMiscEntryFlow(proxy); - return proxy; - }); - } + result = handleObjectStamp(stamp, node); } else { - AnalysisError.shouldNotReachHere("Unsupported stamp " + s); + AnalysisError.shouldNotReachHere("Unsupported stamp " + s + ", node " + node + ", class " + node.getClass().getName()); } - flows.put(node, result); } return result; } + protected TypeFlowBuilder handleObjectStamp(ObjectStamp stamp, ValueNode node) { + if (stamp.isEmpty()) { + throw AnalysisError.shouldNotReachHere("Stamp for node " + node + " is empty."); + } + AnalysisType stampType = (AnalysisType) StampTool.typeOrNull(stamp, bb.getMetaAccess()); + if (stamp.isExactType()) { + /* + * We are lucky: the stamp tells us which type the node has. Happens e.g. for a + * predicated boxed node. + */ + return TypeFlowBuilder.create(bb, method, getPredicate(), node, SourceTypeFlow.class, () -> { + SourceTypeFlow src = new SourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), stampType, !stamp.nonNull()); + flowsGraph.addMiscEntryFlow(src); + return src; + }); + } else { + /* + * Use a type state which consists of all allocated types (which are compatible to + * the node's type). It is a conservative assumption. + */ + TypeFlowBuilder predicate = getPredicate(); + return TypeFlowBuilder.create(bb, method, predicate, node, TypeFlow.class, () -> { + TypeFlow proxy = bb.analysisPolicy().proxy(AbstractAnalysisEngine.sourcePosition(node), stampType.getTypeFlow(bb, true)); + flowsGraph.addMiscEntryFlow(proxy); + return maybePatchAllInstantiated(proxy, stampType, predicate); + }); + } + } + + protected TypeFlowBuilder handleIntegerStamp(IntegerStamp stamp, ValueNode node) { + AnalysisType type = getNodeType(node); + long lo = stamp.lowerBound(); + long hi = stamp.upperBound(); + if (lo == hi) { + return TypeFlowBuilder.create(bb, method, getPredicate(), node, ConstantPrimitiveSourceTypeFlow.class, () -> { + var flow = new ConstantPrimitiveSourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, lo); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + } else if (getPredicate() != alwaysEnabled) { + /* + * It only makes sense to create a local version of any primitive flow if it will be + * guarded by a more specific predicate than alwaysEnabled. Otherwise, use the + * global singleton. + */ + return TypeFlowBuilder.create(bb, method, getPredicate(), node, AnyPrimitiveSourceTypeFlow.class, () -> { + var flow = new AnyPrimitiveSourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + } else { + return anyPrimitiveSourceTypeFlowBuilder; + } + } + public void add(ValueNode node, TypeFlowBuilder flow) { assert !contains(node) : node; flows.put(typeFlowUnproxify(node), flow); @@ -789,8 +879,8 @@ public boolean merge(AbstractMergeNode merge, List withStates) break; } else if (mergeFlow != newFlow) { if (newFlow == oldFlow) { - newFlow = TypeFlowBuilder.create(bb, merge, MergeTypeFlow.class, () -> { - MergeTypeFlow newMergeFlow = new MergeTypeFlow(AbstractAnalysisEngine.sourcePosition(merge)); + newFlow = TypeFlowBuilder.create(bb, method, null, merge, MergeTypeFlow.class, () -> { + MergeTypeFlow newMergeFlow = new MergeTypeFlow(AbstractAnalysisEngine.sourcePosition(merge), mergeFlow.get().getDeclaredType()); flowsGraph.addMiscEntryFlow(newMergeFlow); return newMergeFlow; }); @@ -806,9 +896,351 @@ public boolean merge(AbstractMergeNode merge, List withStates) } @Override + @SuppressFBWarnings(value = "CN_IDIOM_NO_SUPER_CALL", justification = "TypeFlowsOfNodes uses the clone method in a non-standard way.") public TypeFlowsOfNodes clone() { return new TypeFlowsOfNodes(this); } + + public void setPredicate(@SuppressWarnings("unused") TypeFlowBuilder predicate) { + // noop + } + + public TypeFlowBuilder getPredicate() { + // noop + return null; + } + + public boolean usePredicates() { + return false; + } + + public void addInvoke(@SuppressWarnings("unused") ValueNode invoke, @SuppressWarnings("unused") TypeFlowBuilder invokeBuilder) { + // no-op + } + + public TypeFlowBuilder getInvoke(@SuppressWarnings("unused") ValueNode invoke) { + // no-op + return null; + } + } + + /** + * An extension of {@link TypeFlowsOfNodes} that contains predicate-specific functionality. + * Also, it materializes many more types of flows compared to its parent. + */ + private class TypeFlowsOfNodesWithPredicates extends TypeFlowsOfNodes implements Cloneable { + + /** + * Keeps a reference to the current predicate, used to establish predicate edges. + */ + private TypeFlowBuilder predicate; + + /** + * Keep a mapping for invokes, so that we can look corresponding TypeFlowBuilders up when + * handling ExceptionObjectNodes, where we want to use the predicate of the invoke as the + * predicate for the exception handler, i.e. the exception handler is reachable iff the + * invoke is. + *

+ * We cannot reuse the {@link TypeFlowsOfNodes#flows}, because it already maps the invoke to + * the actual return builder and here we want the invoke builder. + */ + private final Map> invokes; + + protected TypeFlowsOfNodesWithPredicates() { + super(); + this.invokes = new HashMap<>(); + } + + protected TypeFlowsOfNodesWithPredicates(TypeFlowsOfNodesWithPredicates copyFrom) { + super(copyFrom); + this.predicate = copyFrom.predicate; + this.invokes = new HashMap<>(copyFrom.invokes); + } + + @Override + public TypeFlowBuilder lookup(ValueNode n) { + ValueNode node = typeFlowUnproxify(n); + TypeFlowBuilder result = flows.get(node); + if (result != null && result.getPredicate() != getPredicate() && n instanceof ParameterNode) { + /* + * If the previous result has outdated predicate, which happens e.g. when a + * different method parameter is assigned into a phi node in each branch, create a + * local anchor to prevent the value from flowing through unreachable branch. + * + * We currently do this only for parameter nodes to support the pattern `cond ? p1 : + * p2`. In the future, we might extend it to other nodes at the cost of introducing + * more LocalAnchorFlows into the graph. + */ + var resultBuilder = result; + var anchorFlow = TypeFlowBuilder.create(bb, method, getPredicate(), result.getSource(), LocalAnchorFlow.class, () -> { + var flow = new LocalAnchorFlow(AbstractAnalysisEngine.syntheticSourcePosition(method), resultBuilder.get().declaredType); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + anchorFlow.addUseDependency(result); + flows.put(node, anchorFlow); + return anchorFlow; + } + if (result == null) { + /* + * There is no type flow set, yet. Therefore, we have no info for the node. + */ + Stamp s = n.stamp(NodeView.DEFAULT); + if (node instanceof ConditionalNode conditionalNode) { + var condition = lookup(conditionalNode.condition()); + var trueValue = lookup(conditionalNode.trueValue()); + var falseValue = lookup(conditionalNode.falseValue()); + var declaredType = ((AnalysisType) s.javaType(bb.getMetaAccess())); + result = TypeFlowBuilder.create(bb, method, getPredicate(), node, ConditionalFlow.class, () -> { + var flow = new ConditionalFlow(AbstractAnalysisEngine.sourcePosition(node), declaredType, condition.get(), trueValue.get(), falseValue.get()); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + result.addObserverDependency(condition); + result.addUseDependency(trueValue); + result.addUseDependency(falseValue); + } else if (node instanceof IntegerEqualsNode equalsNode) { + var x = lookup(equalsNode.getX()); + var y = lookup(equalsNode.getY()); + var type = getNodeType(equalsNode); + result = TypeFlowBuilder.create(bb, method, getPredicate(), node, BooleanPrimitiveCheckTypeFlow.class, () -> { + var flow = new BooleanPrimitiveCheckTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, x.get(), y.get(), PrimitiveComparison.EQ); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + result.addUseDependency(x); + result.addUseDependency(y); + } else if (node instanceof IntegerLowerThanNode lowerThan) { + var x = lookup(lowerThan.getX()); + var y = lookup(lowerThan.getY()); + var type = getNodeType(lowerThan); + result = TypeFlowBuilder.create(bb, method, getPredicate(), node, BooleanPrimitiveCheckTypeFlow.class, () -> { + var flow = new BooleanPrimitiveCheckTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, x.get(), y.get(), PrimitiveComparison.LT); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + result.addUseDependency(x); + result.addUseDependency(y); + } else if (node instanceof IsNullNode isNull) { + ValueNode object = isNull.getValue(); + TypeFlowBuilder inputBuilder = lookup(object); + var type = getNodeType(isNull); + result = TypeFlowBuilder.create(bb, method, getPredicate(), node, BooleanNullCheckTypeFlow.class, () -> { + var flow = new BooleanNullCheckTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + result.addUseDependency(inputBuilder); + } else if (node instanceof InstanceOfNode instanceOf) { + ValueNode object = instanceOf.getValue(); + TypeReference typeReference = instanceOf.type(); + var type = (AnalysisType) instanceOf.type().getType(); + TypeFlowBuilder objectBuilder = lookup(object); + result = TypeFlowBuilder.create(bb, method, getPredicate(), node, BooleanInstanceOfCheckTypeFlow.class, () -> { + var flow = new BooleanInstanceOfCheckTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, typeReference.isExact(), instanceOf.allowsNull(), bb.getLongType()); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + result.addUseDependency(objectBuilder); + } else if (node instanceof ConstantNode && node.asJavaConstant().isNull()) { + result = TypeFlowBuilder.create(bb, method, getPredicate(), node, ConstantTypeFlow.class, () -> { + ConstantTypeFlow constantSource = new ConstantTypeFlow(AbstractAnalysisEngine.sourcePosition(node), null, TypeState.forNull()); + flowsGraph.addMiscEntryFlow(constantSource); + return constantSource; + }); + } else if (node instanceof ConstantNode && node.asJavaConstant().getJavaKind() == JavaKind.Object) { + assert StampTool.isExactType(node) : node; + AnalysisType type = (AnalysisType) StampTool.typeOrNull(node, bb.getMetaAccess()); + result = TypeFlowBuilder.create(bb, method, getPredicate(), node, ConstantTypeFlow.class, () -> { + JavaConstant constantValue = node.asJavaConstant(); + BytecodePosition position = AbstractAnalysisEngine.sourcePosition(node); + JavaConstant heapConstant = bb.getUniverse().getHeapScanner().toImageHeapObject(constantValue, new EmbeddedRootScan(position, constantValue)); + ConstantTypeFlow constantSource = new ConstantTypeFlow(position, type, TypeState.forConstant(bb, heapConstant, type)); + flowsGraph.addMiscEntryFlow(constantSource); + return constantSource; + }); + } else if (s instanceof IntegerStamp stamp) { + result = handleIntegerStamp(stamp, node); + } else if (s instanceof ObjectStamp stamp) { + result = handleObjectStamp(stamp, node); + } else { + assert s instanceof VoidStamp : "The only remaining case should be a void stamp: " + s; + /* + * GR-58471: Some of the nodes below might still be handled, some of them maybe + * should not be encountered (e.g. ShortCircuitOrNode). + */ + assert node instanceof ClassIsArrayNode || node instanceof ClassIsAssignableFromNode || node instanceof ShortCircuitOrNode || node instanceof ObjectEqualsNode || + node instanceof FloatEqualsNode || + node instanceof FloatLessThanNode || + node instanceof IntegerTestNode || node instanceof InstanceOfDynamicNode || + node instanceof ObjectIsArrayNode : "Unsupported combination of stamp " + s + ", node " + node + ", class " + node.getClass().getName(); + var type = getNodeType(node); + if (getPredicate() != null) { + result = TypeFlowBuilder.create(bb, method, getPredicate(), node, AnyPrimitiveSourceTypeFlow.class, () -> { + var flow = new AnyPrimitiveSourceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + } else { + result = anyPrimitiveSourceTypeFlowBuilder; + } + + } + flows.put(node, result); + } + return result; + } + + @Override + public void addInvoke(ValueNode invoke, TypeFlowBuilder invokeBuilder) { + invokes.put(invoke, invokeBuilder); + } + + @Override + public TypeFlowBuilder getInvoke(ValueNode invoke) { + return invokes.get(invoke); + } + + @Override + public boolean merge(AbstractMergeNode merge, List withStates) { + for (AbstractEndNode end : merge.forwardEnds()) { + if (!processedNodes.contains(end)) { + return false; + } + } + + var oldPredicate = getPredicate(); + /* + * If there are no other states to merge, we can just keep the current predicate. + * Otherwise, we create a PredicateMergeFlow. + */ + if (!withStates.isEmpty()) { + var predicates = new ArrayList>(); + predicates.add(getPredicate()); + for (TypeFlowsOfNodes other : withStates) { + if (other.getPredicate() != null) { + predicates.add(other.getPredicate()); + } else { + break; + } + } + if (predicates.size() == withStates.size() + 1) { + assert predicates.size() > 1 : "PredMerge with 1 input is not necessary, just reuse the input."; + var predicateMergeFlow = TypeFlowBuilder.create(bb, method, predicates, merge, PredicateMergeFlow.class, () -> { + var flow = new PredicateMergeFlow(AbstractAnalysisEngine.sourcePosition(merge)); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + setPredicate(predicateMergeFlow); + } else { + setPredicate(alwaysEnabled); + } + } + assert getPredicate() != null : "Each merge should have a predicate."; + + Iterator>> iterator = flows.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + Node node = entry.getKey(); + TypeFlowBuilder oldFlow = entry.getValue(); + if (shouldAddAnchorFlow(oldFlow, oldPredicate)) { + oldFlow = anchorFlow(merge, oldPredicate, oldFlow); + } + TypeFlowBuilder newFlow = oldFlow; + + for (TypeFlowsOfNodes other : withStates) { + TypeFlowBuilder mergeFlow = other.flows.get(node); + + if (mergeFlow == null) { + iterator.remove(); + break; + } else if (mergeFlow != newFlow) { + if (newFlow == oldFlow) { + TypeFlowBuilder m = mergeFlow; + newFlow = TypeFlowBuilder.create(bb, method, getPredicate(), merge, MergeTypeFlow.class, () -> { + MergeTypeFlow newMergeFlow = new MergeTypeFlow(AbstractAnalysisEngine.sourcePosition(merge), m.get().getDeclaredType()); + flowsGraph.addMiscEntryFlow(newMergeFlow); + return newMergeFlow; + }); + newFlow.addUseDependency(oldFlow); + entry.setValue(newFlow); + } + if (shouldAddAnchorFlow(mergeFlow, other.getPredicate())) { + mergeFlow = anchorFlow(merge, other.getPredicate(), mergeFlow); + } + newFlow.addUseDependency(mergeFlow); + } + } + } + + return true; + } + + /** + * Test whether it is worth it to add a LocalAnchorFlow for a given flow. LocalAnchorFlows + * are beneficial if the branch can never return, which happens due to always failing method + * invocation, or infinite loop. To prevent from introducing too many LocalAnchorFlows, we + * only materialize them for ActualReturnTypeFlow (results of method invocations) or + * PredicateMergeFlows. + */ + private boolean shouldAddAnchorFlow(TypeFlowBuilder flow, TypeFlowBuilder branchPredicate) { + if (branchPredicate.getFlowClass() == PredicateMergeFlow.class || branchPredicate.getFlowClass() == ActualReturnTypeFlow.class) { + if (flow.getFlowClass() == FilterTypeFlow.class || flow.getFlowClass() == NullCheckTypeFlow.class || flow.getFlowClass() == PrimitiveFilterTypeFlow.class) { + return flow.getPredicate() != branchPredicate; + } + } + return false; + } + + /** + * Create a flow anchored at the end of a given branch that is enabled by the latest + * predicate in that branch. + */ + private TypeFlowBuilder anchorFlow(AbstractMergeNode merge, TypeFlowBuilder currentPredicate, TypeFlowBuilder oldFlow) { + var anchorFlow = TypeFlowBuilder.create(bb, method, currentPredicate, merge, LocalAnchorFlow.class, () -> { + var flow = new LocalAnchorFlow(AbstractAnalysisEngine.syntheticSourcePosition(merge, method), oldFlow.get().declaredType); + flowsGraph.addMiscEntryFlow(flow); + return flow; + }); + anchorFlow.addUseDependency(oldFlow); + return anchorFlow; + } + + @Override + public void loopEnds(LoopBeginNode loopBegin, List loopEndStates) { + for (ValuePhiNode phi : loopBegin.valuePhis()) { + if (bb.isSupportedJavaKind(phi.getStackKind())) { + var mergeFlow = loopPhiFlows.get(phi); + for (int i = 0; i < loopEndStates.size(); i++) { + ValueNode valueNode = phi.valueAt(i + 1); + var x = loopEndStates.get(i).lookup(valueNode); + mergeFlow.addUseDependency(x); + } + update(phi, mergeFlow); + } + } + } + + @Override + @SuppressFBWarnings(value = "CN_IDIOM_NO_SUPER_CALL", justification = "TypeFlowsOfNodes uses the clone method in a non-standard way.") + public TypeFlowsOfNodes clone() { + return new TypeFlowsOfNodesWithPredicates(this); + } + + @Override + public void setPredicate(TypeFlowBuilder predicate) { + this.predicate = predicate; + } + + @Override + public TypeFlowBuilder getPredicate() { + return this.predicate; + } + + @Override + public boolean usePredicates() { + return true; + } } /** @@ -832,8 +1264,8 @@ class NodeIterator extends PostOrderNodeIterator { private TypeFlowBuilder uniqueReturnFlowBuilder(ReturnNode node) { if (returnBuilder == null) { AnalysisType returnType = method.getSignature().getReturnType(); - if (bb.isSupportedJavaKind(returnType.getJavaKind())) { - returnBuilder = TypeFlowBuilder.create(bb, node, FormalReturnTypeFlow.class, () -> { + if (bb.isSupportedJavaKind(returnType.getJavaKind()) || (state.usePredicates() && returnType.getJavaKind() == JavaKind.Void)) { + returnBuilder = TypeFlowBuilder.create(bb, method, alwaysEnabled, node, FormalReturnTypeFlow.class, () -> { FormalReturnTypeFlow returnFlow = flowsGraph.getReturnFlow(); if (returnFlow != null) { // updating source to reflect position in new parsing of code @@ -855,33 +1287,76 @@ private TypeFlowBuilder uniqueReturnFlowBuilder(ReturnNode node) { return returnBuilder; } + private void handleCompareNode(ValueNode source, CompareNode condition, PrimitiveComparison comparison) { + var xNode = typeFlowUnproxify(condition.getX()); + var yNode = typeFlowUnproxify(condition.getY()); + var xConstant = xNode.isConstant(); + var yConstant = yNode.isConstant(); + var xFlow = state.lookup(xNode); + var yFlow = state.lookup(yNode); + if (!xConstant) { + var leftFlowBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), source, PrimitiveFilterTypeFlow.class, () -> { + var flow = new PrimitiveFilterTypeFlow(AbstractAnalysisEngine.sourcePosition(source), xFlow.get().declaredType, xFlow.get(), yFlow.get(), comparison); + if (yConstant) { + flowsGraph.addNodeFlow(source, flow); + } + return flow; + }); + leftFlowBuilder.addUseDependency(xFlow); + leftFlowBuilder.addUseDependency(yFlow); + typeFlowGraphBuilder.registerSinkBuilder(leftFlowBuilder); + state.update(xNode, leftFlowBuilder); + state.setPredicate(leftFlowBuilder); + } + if (!yConstant) { + var rightFlowBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), source, PrimitiveFilterTypeFlow.class, () -> { + var flow = new PrimitiveFilterTypeFlow(AbstractAnalysisEngine.sourcePosition(source), yFlow.get().declaredType, yFlow.get(), xFlow.get(), comparison.flip()); + flowsGraph.addNodeFlow(source, flow); + return flow; + + }); + rightFlowBuilder.addUseDependency(yFlow); + rightFlowBuilder.addUseDependency(xFlow); + typeFlowGraphBuilder.registerSinkBuilder(rightFlowBuilder); + state.update(yNode, rightFlowBuilder); + state.setPredicate(rightFlowBuilder); + } + } + + private void handleUnaryOpLogicNode(UnaryOpLogicNode condition, TypeFlowBuilder builder) { + ValueNode object = condition.getValue(); + TypeFlowBuilder inputBuilder = state.lookup(object); + builder.addUseDependency(inputBuilder); + typeFlowGraphBuilder.registerSinkBuilder(builder); + state.update(object, builder); + state.setPredicate(builder); + } + private void handleCondition(ValueNode source, LogicNode condition, boolean isTrue) { - if (condition instanceof IsNullNode) { - IsNullNode nullCheck = (IsNullNode) condition; - ValueNode object = nullCheck.getValue(); - TypeFlowBuilder inputBuilder = state.lookup(object); - TypeFlowBuilder nullCheckBuilder = TypeFlowBuilder.create(bb, source, NullCheckTypeFlow.class, () -> { + if (state.usePredicates()) { + if (condition instanceof IntegerLowerThanNode lowerThan) { + handleCompareNode(source, lowerThan, isTrue ? PrimitiveComparison.LT : PrimitiveComparison.GE); + } else if (condition instanceof IntegerEqualsNode equalsNode) { + handleCompareNode(source, equalsNode, isTrue ? PrimitiveComparison.EQ : PrimitiveComparison.NEQ); + } + } + if (condition instanceof IsNullNode nullCheck) { + var inputBuilder = state.lookup(nullCheck.getValue()); + var nullCheckBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), source, NullCheckTypeFlow.class, () -> { NullCheckTypeFlow nullCheckFlow = new NullCheckTypeFlow(AbstractAnalysisEngine.sourcePosition(source), inputBuilder.get().getDeclaredType(), !isTrue); flowsGraph.addNodeFlow(source, nullCheckFlow); return nullCheckFlow; }); - nullCheckBuilder.addUseDependency(inputBuilder); - typeFlowGraphBuilder.registerSinkBuilder(nullCheckBuilder); - state.update(object, nullCheckBuilder); - - } else if (condition instanceof InstanceOfNode) { - InstanceOfNode instanceOf = (InstanceOfNode) condition; - ValueNode object = instanceOf.getValue(); - TypeReference typeReference = instanceOf.type(); - AnalysisType type = (AnalysisType) instanceOf.type().getType(); - TypeFlowBuilder filterBuilder = TypeFlowBuilder.create(bb, source, FilterTypeFlow.class, () -> { + handleUnaryOpLogicNode(nullCheck, nullCheckBuilder); + } else if (condition instanceof InstanceOfNode instanceOf) { + var typeReference = instanceOf.type(); + var type = (AnalysisType) instanceOf.type().getType(); + var filterBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), source, FilterTypeFlow.class, () -> { FilterTypeFlow filterFlow = new FilterTypeFlow(AbstractAnalysisEngine.sourcePosition(source), type, typeReference.isExact(), isTrue, !isTrue ^ instanceOf.allowsNull()); flowsGraph.addNodeFlow(source, filterFlow); return filterFlow; }); - filterBuilder.addUseDependency(state.lookup(object)); - typeFlowGraphBuilder.registerSinkBuilder(filterBuilder); - state.update(object, filterBuilder); + handleUnaryOpLogicNode(instanceOf, filterBuilder); } } @@ -893,22 +1368,24 @@ protected void node(FixedNode n) { if (delegateNodeProcessing(n, state)) { // processed by subclass return; - } else if (n instanceof LoopEndNode) { - LoopEndNode end = (LoopEndNode) n; + } else if (n instanceof LoopEndNode end) { LoopBeginNode merge = end.loopBegin(); int predIdx = merge.phiPredecessorIndex(end); for (PhiNode phi : merge.phis()) { if (bb.isSupportedJavaKind(phi.getStackKind())) { + /* + * Looking up the input of a phi node at the end of its branch ensures it is + * created with the appropriate predicate. + */ loopPhiFlows.get(phi).addUseDependency(state.lookup(phi.valueAt(predIdx))); } } - } else if (n instanceof LoopBeginNode) { - LoopBeginNode merge = (LoopBeginNode) n; + } else if (n instanceof LoopBeginNode merge) { for (PhiNode phi : merge.phis()) { if (bb.isSupportedJavaKind(phi.getStackKind())) { - TypeFlowBuilder newFlowBuilder = TypeFlowBuilder.create(bb, merge, MergeTypeFlow.class, () -> { - MergeTypeFlow newFlow = new MergeTypeFlow(AbstractAnalysisEngine.sourcePosition(merge)); + TypeFlowBuilder newFlowBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), merge, MergeTypeFlow.class, () -> { + MergeTypeFlow newFlow = new MergeTypeFlow(AbstractAnalysisEngine.sourcePosition(merge), getNodeType(phi)); flowsGraph.addMiscEntryFlow(newFlow); return newFlow; }); @@ -922,32 +1399,42 @@ protected void node(FixedNode n) { } } - } else if (n instanceof EndNode) { - EndNode end = (EndNode) n; + } else if (n instanceof EndNode end) { AbstractMergeNode merge = end.merge(); int predIdx = merge.phiPredecessorIndex(end); for (PhiNode phi : merge.phis()) { if (bb.isSupportedJavaKind(phi.getStackKind())) { + /* + * Looking up the input of a phi node at the end of its branch ensures it is + * created with the appropriate predicate. + */ state.add(phi, state.lookup(phi.valueAt(predIdx))); } } - } else if (n instanceof ExceptionObjectNode) { - ExceptionObjectNode node = (ExceptionObjectNode) n; - TypeFlowBuilder exceptionObjectBuilder = TypeFlowBuilder.create(bb, node, TypeFlow.class, () -> { - TypeFlow input = ((AnalysisType) StampTool.typeOrNull(node, bb.getMetaAccess())).getTypeFlow(bb, false); + } else if (n instanceof ExceptionObjectNode node) { + /* + * We are entering an exception handler. This block can be reachable even when the + * method return type state is empty (when it always throws exception). + */ + if (n.predecessor() instanceof Invoke invoke) { + TypeFlowBuilder invokeBuilder = state.getInvoke(invoke.asFixedNode()); + state.setPredicate(invokeBuilder != null ? ((TypeFlowBuilder) invokeBuilder.getPredicate()) : alwaysEnabled); + } else { + state.setPredicate(alwaysEnabled); + } + TypeFlowBuilder predicate = state.getPredicate(); + TypeFlowBuilder exceptionObjectBuilder = TypeFlowBuilder.create(bb, method, predicate, node, TypeFlow.class, () -> { + AnalysisType analysisType = (AnalysisType) StampTool.typeOrNull(node, bb.getMetaAccess()); + TypeFlow input = analysisType.getTypeFlow(bb, false); TypeFlow exceptionObjectFlow = bb.analysisPolicy().proxy(AbstractAnalysisEngine.sourcePosition(node), input); flowsGraph.addMiscEntryFlow(exceptionObjectFlow); - return exceptionObjectFlow; + return maybePatchAllInstantiated(exceptionObjectFlow, analysisType, predicate); }); state.add(node, exceptionObjectBuilder); - } else if (n instanceof BeginNode) { - BeginNode node = (BeginNode) n; - Node pred = node.predecessor(); - - if (pred instanceof IfNode) { - IfNode ifNode = (IfNode) pred; + } else if (n instanceof AbstractBeginNode node) { + if (node.predecessor() instanceof IfNode ifNode) { handleCondition(node, ifNode.condition(), node == ifNode.trueSuccessor()); } @@ -955,22 +1442,29 @@ protected void node(FixedNode n) { FixedGuardNode node = (FixedGuardNode) n; handleCondition(node, node.condition(), !node.isNegated()); - } else if (n instanceof ReturnNode) { + } else if (n instanceof ReturnNode node) { /* * Return type flows within the graph do not need to be linked if the method has * opaque return. */ if (!method.hasOpaqueReturn()) { - ReturnNode node = (ReturnNode) n; if (node.result() != null && bb.isSupportedJavaKind(node.result().getStackKind())) { TypeFlowBuilder returnFlowBuilder = uniqueReturnFlowBuilder(node); returnFlowBuilder.addUseDependency(state.lookup(node.result())); + } else if (state.usePredicates() && method.getSignature().getReturnType().getJavaKind() == JavaKind.Void) { + TypeFlowBuilder returnFlowBuilder = uniqueReturnFlowBuilder(node); + /* + * This return is reachable iff the latest predicate has non-empty + * TypeState, which we encode by a use edge between the predicate and the + * ReturnFlow. + */ + returnFlowBuilder.addUseDependency(state.getPredicate()); } } } else if (n instanceof CommitAllocationNode) { processCommitAllocation((CommitAllocationNode) n, state); } else if (n instanceof NewInstanceNode) { - processNewInstance((NewInstanceNode) n, state); + processNewInstance((NewInstanceNode) n, state, true); } else if (n instanceof DynamicNewInstanceNode) { DynamicNewInstanceNode node = (DynamicNewInstanceNode) n; ValueNode instanceTypeNode = node.getInstanceType(); @@ -994,17 +1488,17 @@ protected void node(FixedNode n) { * generate a heap object for each instantiated type. */ instanceType = bb.getObjectType(); - instanceTypeBuilder = TypeFlowBuilder.create(bb, instanceType, AllInstantiatedTypeFlow.class, () -> ((AllInstantiatedTypeFlow) instanceType.getTypeFlow(bb, false))); + TypeFlowBuilder predicate = state.getPredicate(); + instanceTypeBuilder = TypeFlowBuilder.create(bb, method, predicate, instanceType, TypeFlow.class, + () -> maybePatchAllInstantiated(instanceType.getTypeFlow(bb, false), instanceType, predicate)); } - TypeFlowBuilder dynamicNewInstanceBuilder = TypeFlowBuilder.create(bb, node, DynamicNewInstanceTypeFlow.class, () -> { + TypeFlowBuilder dynamicNewInstanceBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, DynamicNewInstanceTypeFlow.class, () -> { DynamicNewInstanceTypeFlow newInstanceTypeFlow = new DynamicNewInstanceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), instanceTypeBuilder.get(), instanceType); flowsGraph.addMiscEntryFlow(newInstanceTypeFlow); return newInstanceTypeFlow; }); - if (instanceTypeNode instanceof GetClassNode) { - dynamicNewInstanceBuilder.addObserverDependency(instanceTypeBuilder); - } + dynamicNewInstanceBuilder.addObserverDependency(instanceTypeBuilder); state.add(node, dynamicNewInstanceBuilder); @@ -1022,7 +1516,7 @@ protected void node(FixedNode n) { */ AnalysisType arrayType = bb.getObjectType(); - TypeFlowBuilder dynamicNewArrayBuilder = TypeFlowBuilder.create(bb, node, DynamicNewInstanceTypeFlow.class, () -> { + TypeFlowBuilder dynamicNewArrayBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, DynamicNewInstanceTypeFlow.class, () -> { DynamicNewInstanceTypeFlow newArrayTypeFlow = new DynamicNewInstanceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), arrayType.getTypeFlow(bb, false), arrayType); flowsGraph.addMiscEntryFlow(newArrayTypeFlow); return newArrayTypeFlow; @@ -1034,8 +1528,8 @@ protected void node(FixedNode n) { AnalysisType type = ((AnalysisType) node.type()); assert type.isInstantiated() : type; - TypeFlowBuilder newArrayBuilder = TypeFlowBuilder.create(bb, node, NewInstanceTypeFlow.class, () -> { - NewInstanceTypeFlow newArray = new NewInstanceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type); + TypeFlowBuilder newArrayBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, NewInstanceTypeFlow.class, () -> { + NewInstanceTypeFlow newArray = new NewInstanceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, true); flowsGraph.addMiscEntryFlow(newArray); return newArray; }); @@ -1088,7 +1582,7 @@ protected void node(FixedNode n) { if (srcBuilder != dstBuilder) { AnalysisType type = (AnalysisType) StampTool.typeOrNull(node.asNode(), bb.getMetaAccess()); - TypeFlowBuilder arrayCopyBuilder = TypeFlowBuilder.create(bb, node, ArrayCopyTypeFlow.class, () -> { + TypeFlowBuilder arrayCopyBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, ArrayCopyTypeFlow.class, () -> { ArrayCopyTypeFlow arrayCopyFlow = new ArrayCopyTypeFlow(AbstractAnalysisEngine.sourcePosition(node.asNode()), type, srcBuilder.get(), dstBuilder.get()); flowsGraph.addMiscEntryFlow(arrayCopyFlow); return arrayCopyFlow; @@ -1117,7 +1611,7 @@ protected void node(FixedNode n) { TypeFlowBuilder inputBuilder = state.lookup(node.getObject()); AnalysisType inputType = (AnalysisType) StampTool.typeOrNull(node.getObject(), bb.getMetaAccess()); - TypeFlowBuilder cloneBuilder = TypeFlowBuilder.create(bb, node, CloneTypeFlow.class, () -> { + TypeFlowBuilder cloneBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, CloneTypeFlow.class, () -> { CloneTypeFlow cloneFlow = new CloneTypeFlow(AbstractAnalysisEngine.sourcePosition(node.asNode()), inputType, inputBuilder.get()); flowsGraph.addMiscEntryFlow(cloneFlow); return cloneFlow; @@ -1198,6 +1692,10 @@ private void modelUnsafeReadAndWriteFlow(ValueNode node, ValueNode object, Value } } + private AnalysisType getNodeType(ValueNode node) { + return (AnalysisType) node.stamp(NodeView.DEFAULT).javaType(bb.getMetaAccess()); + } + @SuppressWarnings("unused") protected boolean delegateNodeProcessing(FixedNode n, TypeFlowsOfNodes state) { // Hook for subclasses to do their own processing. @@ -1289,7 +1787,7 @@ protected void processMethodInvocation(TypeFlowsOfNodes state, ValueNode invoke, } } - TypeFlowBuilder invokeBuilder = TypeFlowBuilder.create(bb, invoke, InvokeTypeFlow.class, () -> { + TypeFlowBuilder invokeBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), invoke, InvokeTypeFlow.class, () -> { TypeFlow[] actualParameters = new TypeFlow[actualParametersBuilders.length]; for (int i = 0; i < actualParameters.length; i++) { @@ -1353,10 +1851,13 @@ protected void processMethodInvocation(TypeFlowsOfNodes state, ValueNode invoke, return invokeFlow; }); - if (!createDeoptInvokeTypeFlow && bb.isSupportedJavaKind(invoke.asNode().getStackKind())) { + state.addInvoke(invoke, invokeBuilder); + + JavaKind stackKind = invoke.asNode().getStackKind(); + if (!createDeoptInvokeTypeFlow && (bb.isSupportedJavaKind(stackKind) || (state.usePredicates() && stackKind == JavaKind.Void))) { /* Create the actual return builder. */ AnalysisType returnType = targetMethod.getSignature().getReturnType(); - TypeFlowBuilder actualReturnBuilder = TypeFlowBuilder.create(bb, invoke.asNode(), ActualReturnTypeFlow.class, () -> { + TypeFlowBuilder actualReturnBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), invoke.asNode(), ActualReturnTypeFlow.class, () -> { InvokeTypeFlow invokeFlow = invokeBuilder.get(); ActualReturnTypeFlow actualReturn = new ActualReturnTypeFlow(invokeFlow.source, returnType); flowsGraph.addMiscEntryFlow(actualReturn); @@ -1368,6 +1869,7 @@ protected void processMethodInvocation(TypeFlowsOfNodes state, ValueNode invoke, actualReturn.setInvokeFlow(invokeFlow); return actualReturn; }); + state.setPredicate(actualReturnBuilder); if (invoke.stamp(NodeView.DEFAULT) instanceof ObjectStamp stamp) { AnalysisType stampType = (AnalysisType) StampTool.typeOrNull(stamp, bb.getMetaAccess()); @@ -1380,13 +1882,14 @@ protected void processMethodInvocation(TypeFlowsOfNodes state, ValueNode invoke, * remove a checkcast that would normally follow the invoke, so we need to * introduce the filter to avoid loosing precision. */ - TypeFlowBuilder filterBuilder = TypeFlowBuilder.create(bb, invoke, FilterTypeFlow.class, () -> { + TypeFlowBuilder filterBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), invoke, FilterTypeFlow.class, () -> { FilterTypeFlow filterFlow = new FilterTypeFlow(invokeLocation, stampType, stamp.isExactType(), true, true); flowsGraph.addMiscEntryFlow(filterFlow); return filterFlow; }); filterBuilder.addUseDependency(actualReturnBuilder); actualReturnBuilder = filterBuilder; + state.setPredicate(actualReturnBuilder); } } @@ -1408,7 +1911,7 @@ protected void processCommitAllocation(CommitAllocationNode commitAllocationNode Map allocatedObjects = new HashMap<>(); for (AllocatedObjectNode allocatedObjectNode : commitAllocationNode.usages().filter(AllocatedObjectNode.class)) { AnalysisType type = (AnalysisType) allocatedObjectNode.getVirtualObject().type(); - processNewInstance(allocatedObjectNode, type, state); + processNewInstance(allocatedObjectNode, type, state, false); allocatedObjects.put(allocatedObjectNode.getVirtualObject(), allocatedObjectNode); } @@ -1447,20 +1950,18 @@ protected void processCommitAllocation(CommitAllocationNode commitAllocationNode assert values.size() == objectStartIndex : values; } - protected void processNewInstance(NewInstanceNode node, TypeFlowsOfNodes state) { + protected void processNewInstance(NewInstanceNode node, TypeFlowsOfNodes state, boolean insertDefaultFieldValues) { /* Instance fields of a new object are initialized to null state in AnalysisField. */ - processNewInstance(node, (AnalysisType) node.instanceClass(), state); + processNewInstance(node, (AnalysisType) node.instanceClass(), state, insertDefaultFieldValues); } protected void processNewArray(NewArrayNode node, TypeFlowsOfNodes state) { - processNewInstance(node, ((AnalysisType) node.elementType()).getArrayClass(), state); + processNewInstance(node, ((AnalysisType) node.elementType()).getArrayClass(), state, false); } - protected void processNewInstance(ValueNode node, AnalysisType type, TypeFlowsOfNodes state) { - assert type.isInstantiated() : type; - - TypeFlowBuilder newInstanceBuilder = TypeFlowBuilder.create(bb, node, NewInstanceTypeFlow.class, () -> { - NewInstanceTypeFlow newInstance = new NewInstanceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type); + protected void processNewInstance(ValueNode node, AnalysisType type, TypeFlowsOfNodes state, boolean insertDefaultFieldValues) { + TypeFlowBuilder newInstanceBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, NewInstanceTypeFlow.class, () -> { + NewInstanceTypeFlow newInstance = new NewInstanceTypeFlow(AbstractAnalysisEngine.sourcePosition(node), type, insertDefaultFieldValues); flowsGraph.addMiscEntryFlow(newInstance); return newInstance; }); @@ -1473,7 +1974,7 @@ protected void processLoadField(ValueNode node, AnalysisField field, ValueNode o if (bb.isSupportedJavaKind(node.getStackKind())) { TypeFlowBuilder loadFieldBuilder; if (field.isStatic()) { - loadFieldBuilder = TypeFlowBuilder.create(bb, node, LoadStaticFieldTypeFlow.class, () -> { + loadFieldBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, LoadStaticFieldTypeFlow.class, () -> { FieldTypeFlow fieldFlow = field.getStaticFieldFlow(); LoadStaticFieldTypeFlow loadFieldFLow = new LoadStaticFieldTypeFlow(AbstractAnalysisEngine.sourcePosition(node), field, fieldFlow); flowsGraph.addNodeFlow(node, loadFieldFLow); @@ -1481,7 +1982,7 @@ protected void processLoadField(ValueNode node, AnalysisField field, ValueNode o }); } else { TypeFlowBuilder objectBuilder = state.lookup(object); - loadFieldBuilder = TypeFlowBuilder.create(bb, node, LoadInstanceFieldTypeFlow.class, () -> { + loadFieldBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, LoadInstanceFieldTypeFlow.class, () -> { LoadInstanceFieldTypeFlow loadFieldFLow = new LoadInstanceFieldTypeFlow(AbstractAnalysisEngine.sourcePosition(node), field, objectBuilder.get()); flowsGraph.addNodeFlow(node, loadFieldFLow); return loadFieldFLow; @@ -1501,7 +2002,7 @@ protected void processStoreField(ValueNode node, AnalysisField field, ValueNode TypeFlowBuilder storeFieldBuilder; if (field.isStatic()) { - storeFieldBuilder = TypeFlowBuilder.create(bb, node, StoreStaticFieldTypeFlow.class, () -> { + storeFieldBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, StoreStaticFieldTypeFlow.class, () -> { FieldTypeFlow fieldFlow = field.getStaticFieldFlow(); StoreStaticFieldTypeFlow storeFieldFlow = new StoreStaticFieldTypeFlow(AbstractAnalysisEngine.sourcePosition(node), field, valueBuilder.get(), fieldFlow); flowsGraph.addMiscEntryFlow(storeFieldFlow); @@ -1509,7 +2010,7 @@ protected void processStoreField(ValueNode node, AnalysisField field, ValueNode }); } else { TypeFlowBuilder objectBuilder = state.lookup(object); - storeFieldBuilder = TypeFlowBuilder.create(bb, node, StoreInstanceFieldTypeFlow.class, () -> { + storeFieldBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, StoreInstanceFieldTypeFlow.class, () -> { StoreInstanceFieldTypeFlow storeFieldFlow = new StoreInstanceFieldTypeFlow(AbstractAnalysisEngine.sourcePosition(node), field, valueBuilder.get(), objectBuilder.get()); flowsGraph.addMiscEntryFlow(storeFieldFlow); return storeFieldFlow; @@ -1529,7 +2030,7 @@ protected void processLoadIndexed(ValueNode node, ValueNode array, TypeFlowsOfNo AnalysisType type = (AnalysisType) StampTool.typeOrNull(array, bb.getMetaAccess()); AnalysisType arrayType = type.isArray() ? type : bb.getObjectArrayType(); - TypeFlowBuilder loadIndexedBuilder = TypeFlowBuilder.create(bb, node, LoadIndexedTypeFlow.class, () -> { + TypeFlowBuilder loadIndexedBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, LoadIndexedTypeFlow.class, () -> { LoadIndexedTypeFlow loadIndexedFlow = new LoadIndexedTypeFlow(AbstractAnalysisEngine.sourcePosition(node), arrayType, arrayBuilder.get()); flowsGraph.addNodeFlow(node, loadIndexedFlow); return loadIndexedFlow; @@ -1549,7 +2050,7 @@ protected void processStoreIndexed(ValueNode node, ValueNode array, ValueNode ne TypeFlowBuilder arrayBuilder = state.lookup(array); TypeFlowBuilder valueBuilder = state.lookupOrAny(newValue, newValueKind); - TypeFlowBuilder storeIndexedBuilder = TypeFlowBuilder.create(bb, node, StoreIndexedTypeFlow.class, () -> { + TypeFlowBuilder storeIndexedBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, StoreIndexedTypeFlow.class, () -> { StoreIndexedTypeFlow storeIndexedFlow = new StoreIndexedTypeFlow(AbstractAnalysisEngine.sourcePosition(node), arrayType, arrayBuilder.get(), valueBuilder.get()); flowsGraph.addMiscEntryFlow(storeIndexedFlow); return storeIndexedFlow; @@ -1571,7 +2072,7 @@ protected void processUnsafeLoad(ValueNode node, ValueNode object, TypeFlowsOfNo * Use the Object type as a conservative approximation for both the receiver object type * and the loaded values type. */ - var loadBuilder = TypeFlowBuilder.create(bb, node, UnsafeLoadTypeFlow.class, () -> { + var loadBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, UnsafeLoadTypeFlow.class, () -> { UnsafeLoadTypeFlow loadTypeFlow = new UnsafeLoadTypeFlow(AbstractAnalysisEngine.sourcePosition(node), bb.getObjectType(), bb.getObjectType(), objectBuilder.get()); flowsGraph.addMiscEntryFlow(loadTypeFlow); return loadTypeFlow; @@ -1592,7 +2093,7 @@ protected void processUnsafeStore(ValueNode node, ValueNode object, ValueNode ne * Use the Object type as a conservative approximation for both the receiver object type * and the stored values type. */ - var storeBuilder = TypeFlowBuilder.create(bb, node, UnsafeStoreTypeFlow.class, () -> { + var storeBuilder = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, UnsafeStoreTypeFlow.class, () -> { UnsafeStoreTypeFlow storeTypeFlow = new UnsafeStoreTypeFlow(AbstractAnalysisEngine.sourcePosition(node), bb.getObjectType(), bb.getObjectType(), objectBuilder.get(), newValueBuilder.get()); flowsGraph.addMiscEntryFlow(storeTypeFlow); @@ -1618,9 +2119,11 @@ protected void processImplicitNonNull(ValueNode node, ValueNode source, TypeFlow assert node.stamp(NodeView.DEFAULT) instanceof AbstractObjectStamp : node; if (!StampTool.isPointerNonNull(node)) { TypeFlowBuilder inputBuilder = state.lookup(node); - TypeFlowBuilder nullCheckBuilder = TypeFlowBuilder.create(bb, source, NullCheckTypeFlow.class, () -> { + TypeFlowBuilder predicate = state.getPredicate(); + TypeFlowBuilder nullCheckBuilder = TypeFlowBuilder.create(bb, method, predicate, source, NullCheckTypeFlow.class, () -> { var inputFlow = inputBuilder.get(); - if (inputFlow instanceof NullCheckTypeFlow nullCheck && nullCheck.isBlockingNull()) { + // only allow if they have the same predicate + if (inputFlow instanceof NullCheckTypeFlow nullCheck && nullCheck.isBlockingNull() && inputBuilder.getPredicate() == predicate) { // unnecessary to create redundant null type check return nullCheck; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/NewInstanceTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/NewInstanceTypeFlow.java index d40e7a50440a..a62d7eec9336 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/NewInstanceTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/NewInstanceTypeFlow.java @@ -31,6 +31,7 @@ import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.flow.context.AnalysisContext; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; +import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.typestate.TypeState; @@ -46,6 +47,13 @@ public class NewInstanceTypeFlow extends TypeFlow { private static final AtomicReferenceFieldUpdater HEAP_OBJECTS_CACHE_UPDATER = // AtomicReferenceFieldUpdater.newUpdater(NewInstanceTypeFlow.class, ConcurrentMap.class, "heapObjectsCache"); + /** + * True iff the flow should insert default values into the fields of the instantiated type. Both + * NewInstanceNode and CommitAllocationNode are represented as a NewInstanceTypeFlow, but we + * want to insert the default values only for NewInstanceNode. + */ + private final boolean insertDefaultFieldValues; + /** * The original type flow keeps track of the heap objects created for the clones to avoid * duplication of heap object abstractions. The allocation context is derived from the allocator @@ -55,11 +63,23 @@ public class NewInstanceTypeFlow extends TypeFlow { */ volatile ConcurrentMap heapObjectsCache; - public NewInstanceTypeFlow(BytecodePosition position, AnalysisType type) { + public NewInstanceTypeFlow(BytecodePosition position, AnalysisType type, boolean insertDefaultFieldValues) { /* The actual type state is set lazily in initFlow(). */ super(position, type, TypeState.forEmpty()); + this.insertDefaultFieldValues = insertDefaultFieldValues; assert source != null; - assert declaredType.isInstantiated() : "Type " + declaredType + " not instantiated " + position; + } + + @Override + protected void onFlowEnabled(PointsToAnalysis bb) { + super.onFlowEnabled(bb); + declaredType.registerAsInstantiated(source); + if (insertDefaultFieldValues) { + for (var f : declaredType.getInstanceFields(true)) { + var field = (AnalysisField) f; + field.getInitialFlow().addState(bb, TypeState.defaultValueForKind(field.getStorageKind())); + } + } } @Override @@ -81,6 +101,7 @@ public boolean needsInitialization() { NewInstanceTypeFlow(PointsToAnalysis bb, NewInstanceTypeFlow original, MethodFlowsGraph methodFlows) { super(original, methodFlows, original.createCloneState(bb, methodFlows)); + this.insertDefaultFieldValues = original.insertDefaultFieldValues; } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PredicateMergeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PredicateMergeFlow.java new file mode 100644 index 000000000000..9b12740dc719 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PredicateMergeFlow.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.typestate.TypeState; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * Corresponds to a control flow merge. It is the only flow with multiple incoming predicates. + *

+ * The code after a control flow merge is reachable iff the latest predicate in any of the incoming + * branches is non-empty. + */ +public class PredicateMergeFlow extends TypeFlow { + public PredicateMergeFlow(BytecodePosition position) { + /* + * The type state is non-empty, but the flow is disabled by default. It will only start + * enabling its predicated flows once it is itself enabled. + */ + super(position, null, TypeState.anyPrimitiveState()); + } + + private PredicateMergeFlow(MethodFlowsGraph methodFlows, PredicateMergeFlow original) { + super(original, methodFlows, TypeState.anyPrimitiveState()); + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new PredicateMergeFlow(methodFlows, this); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveComparison.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveComparison.java new file mode 100644 index 000000000000..a53b78e199bf --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveComparison.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.NodeIterator; + +/** + * Represents a primitive comparison used in {@link PrimitiveFilterTypeFlow} and + * {@link BooleanPrimitiveCheckTypeFlow}. We have to handle negated and flipped versions of the + * comparison, so using only canonical operations is not enough. + */ +public enum PrimitiveComparison { + EQ("=="), + NEQ("!="), + LT("<"), + GE(">="), + GT(">"), + LE("<="); + + public final String label; + + PrimitiveComparison(String label) { + this.label = label; + } + + /** + * Returns the negated version of this operation, e.g. '<' will become '>=', which is used when + * handling the else branches of IfNodes. + */ + public PrimitiveComparison negate() { + return switch (this) { + case EQ -> NEQ; + case NEQ -> EQ; + case LT -> GE; + case GE -> LT; + case GT -> LE; + case LE -> GT; + }; + } + + /** + * 'Rotates' the operation, so that e.g. '<' will become '>', which is used when filtering y + * with respect to x < y when handling a CompareNode in {@link NodeIterator}. Note that the + * result is different from negating the operation, where '<' would become '>='. + */ + public PrimitiveComparison flip() { + return switch (this) { + case EQ -> EQ; + case NEQ -> NEQ; + case LT -> GT; + case GT -> LT; + case LE -> GE; + case GE -> LE; + }; + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveFilterTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveFilterTypeFlow.java new file mode 100644 index 000000000000..0045fb47e211 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/PrimitiveFilterTypeFlow.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.flow; + +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.typestate.TypeState; + +import jdk.vm.ci.code.BytecodePosition; + +/** + * This flow represents a binary comparison of two values, left and right. + * It filters the value of left using the condition and + * right. Typically, two instances PrimitiveFilterTypeFlow are created for each + * condition x cmp y. The first one filters x with respect to + * op and y, and the second filters y with respect to + * op and x. + */ +public class PrimitiveFilterTypeFlow extends TypeFlow { + + private final TypeFlow left; + private final TypeFlow right; + private final PrimitiveComparison comparison; + + public PrimitiveFilterTypeFlow(BytecodePosition position, AnalysisType declaredType, TypeFlow left, TypeFlow right, PrimitiveComparison comparison) { + super(position, declaredType); + this.left = left; + this.right = right; + this.comparison = comparison; + } + + private PrimitiveFilterTypeFlow(PointsToAnalysis bb, MethodFlowsGraph methodFlows, PrimitiveFilterTypeFlow original) { + super(original, methodFlows); + this.left = methodFlows.lookupCloneOf(bb, original.left); + this.right = methodFlows.lookupCloneOf(bb, original.right); + this.comparison = original.comparison; + } + + @Override + public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph methodFlows) { + return new PrimitiveFilterTypeFlow(bb, methodFlows, this); + } + + @Override + public boolean addState(PointsToAnalysis bb, TypeState add) { + return super.addState(bb, eval()); + } + + @Override + protected void onInputSaturated(PointsToAnalysis bb, TypeFlow input) { + /* + * If an input saturated, it does not mean that the condition has to always saturate as + * well, e.g. Any == 5 still returns 5. + */ + super.addState(bb, eval()); + } + + /** + * Filters the type state of left using condition and right. + */ + private TypeState eval() { + var leftState = left.isSaturated() ? TypeState.anyPrimitiveState() : left.getState(); + var rightState = right.isSaturated() ? TypeState.anyPrimitiveState() : right.getState(); + assert leftState.isPrimitive() || leftState.isEmpty() : left; + assert rightState.isPrimitive() || rightState.isEmpty() : right; + return TypeState.filter(leftState, comparison, rightState); + } + + public PrimitiveComparison getComparison() { + return comparison; + } + + public TypeFlow getLeft() { + return left; + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java index c31c0fdc6a02..52aadbe30d58 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java @@ -26,6 +26,7 @@ import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import com.oracle.graal.pointsto.PointsToAnalysis; @@ -35,21 +36,66 @@ import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.results.StrengthenGraphs; import com.oracle.graal.pointsto.typestate.PointsToStats; -import com.oracle.graal.pointsto.typestate.PrimitiveConstantTypeState; +import com.oracle.graal.pointsto.typestate.PrimitiveTypeState; import com.oracle.graal.pointsto.typestate.TypeState; import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.graal.pointsto.util.AtomicUtils; import com.oracle.graal.pointsto.util.ConcurrentLightHashSet; import com.oracle.svm.util.ClassUtil; import jdk.graal.compiler.graph.Node; import jdk.vm.ci.code.BytecodePosition; - +import jdk.vm.ci.meta.JavaKind; + +/** + * This class represents a node (further called flow) in the type flow graph. Each flow typically + * corresponds to a Graal IR node in a particular method or some 'global' value such as the value of + * a given field ({@link FieldTypeFlow}) or all instantiatiated subtypes of a specific type + * ({@link AllInstantiatedTypeFlow}). + *

+ * Each node has a {@link TypeState} modelling all the values that can be assigned to the + * instruction or memory location given node represents. Nodes are connected via use, observer and + * predicate edges. + *

+ * Each flow can be in exactly one of the following states: disabled, enabled, or saturated. + *

+ * Disabled nodes can accept values from incoming use edges and remember if their dependencies + * saturated, but they neither propagate any value further down along the use edges nor perform any + * other action such as method linking until they become enabled. + *

+ * A flow can be enabled by its predicate when the predicate itself is enabled and predicate's + * {@link TypeState} becomes non-empty. This process is started by calling + * {@link TypeFlow#enableFlow}, which atomically marks given flow as enabled and triggers the + * {@link TypeFlow#onFlowEnabled} callback, which performs any flow-specific action that should be + * done at this stage such as method linking or marking a type as instantiated. + *

+ * An enabled flow can become saturated when the size of its {@link TypeState} exceeds a given + * configurable threshold, which is checked by calling {@link TypeFlow#checkSaturated}. When a flow + * saturates, it notifies all its dependencies and disconnects itself from the graph. Note that its + * {@link TypeState} no longer matters at this point, therefore if one wants to query a state of a + * given flow, first check the saturation status via {@link TypeFlow#isSaturated()} and only then + * examine its {@link TypeState}. + *

+ * Valid state transitions are as follows: + *

+ * disabled -> enabled -> saturated + *

+ * The transitions are always in this direction. Enabled flow cannot be disabled. Disabled flow + * cannot saturate. + *

+ */ @SuppressWarnings("rawtypes") public abstract class TypeFlow { private static final AtomicReferenceFieldUpdater USE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(TypeFlow.class, Object.class, "uses"); private static final AtomicReferenceFieldUpdater INPUTS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(TypeFlow.class, Object.class, "inputs"); private static final AtomicReferenceFieldUpdater OBSERVERS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(TypeFlow.class, Object.class, "observers"); private static final AtomicReferenceFieldUpdater OBSERVEES_UPDATER = AtomicReferenceFieldUpdater.newUpdater(TypeFlow.class, Object.class, "observees"); + private static final AtomicReferenceFieldUpdater PREDICATED_FLOWS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(TypeFlow.class, Object.class, "predicatedFlows"); + private static final AtomicIntegerFieldUpdater PREDICATE_TRIGGERED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(TypeFlow.class, "predicateTriggered"); + private static final AtomicIntegerFieldUpdater IS_ENABLED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(TypeFlow.class, "isEnabled"); + private static final AtomicReferenceFieldUpdater PREDICATE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(TypeFlow.class, TypeFlow.class, "predicate"); + private static final AtomicIntegerFieldUpdater SATURATED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(TypeFlow.class, "isSaturated"); + protected static final AtomicInteger nextId = new AtomicInteger(); protected final int id; @@ -63,6 +109,18 @@ public abstract class TypeFlow { protected volatile TypeState state; + /** Nonzero iff this flow was enabled by its predicate. */ + @SuppressWarnings("unused") private volatile int isEnabled; + + /** A reference to the predicate of this flow. */ + @SuppressWarnings("unused") private volatile TypeFlow predicate; + + /** + * Nonzero if this flow already triggered and cleared its predicate edges. Used to avoid + * executing the content of{@link TypeFlow#enablePredicated} redundantly. + */ + @SuppressWarnings("unused") private volatile int predicateTriggered; + /** The set of all {@link TypeFlow}s that need to be update when this flow changes. */ @SuppressWarnings("unused") private volatile Object uses; @@ -75,6 +133,12 @@ public abstract class TypeFlow { /** The set of all observees, i.e., objects that notify this flow when they change. */ @SuppressWarnings("unused") private volatile Object observees; + /** + * The set of all predicated flows, i.e., flows that should be enabled when this flows' state + * becomes truthy. + */ + @SuppressWarnings("unused") private volatile Object predicatedFlows; + private int slot; private final boolean isClone; // true -> clone, false -> original protected final MethodFlowsGraph graphRef; @@ -100,7 +164,19 @@ public abstract class TypeFlow { *

* The initial value is false, i.e., the flow is initially not saturated. */ - private volatile boolean isSaturated; + @SuppressWarnings("unused") private volatile int isSaturated; + + /** + * True iff the input of this flow saturated. Used to delay the execution of the + * {@link TypeFlow#onInputSaturated} until this flow is enabled. + */ + protected volatile boolean inputSaturated; + + /** + * True iff the observed value saturated. Used to delay the execution of the + * {@link TypeFlow#onObservedSaturated} until this flow is enabled. + */ + protected volatile boolean observedSaturated; /** * A TypeFlow is invalidated when the flowsgraph it belongs to is updated due to @@ -115,6 +191,7 @@ public abstract class TypeFlow { protected final boolean isPrimitiveFlow; + @SuppressWarnings("this-escape") private TypeFlow(T source, AnalysisType declaredType, TypeState typeState, int slot, boolean isClone, MethodFlowsGraph graphRef) { this.id = nextId.incrementAndGet(); this.source = source; @@ -130,7 +207,93 @@ private TypeFlow(T source, AnalysisType declaredType, TypeState typeState, int s isPrimitiveFlow = typeState.isPrimitive(); } validateSource(); - assert primitiveFlowCheck(state) : this; + assert primitiveFlowCheck(state) : state + ", " + this; + if (this instanceof GlobalFlow) { + /* Global flows should be enabled immediately. */ + AtomicUtils.atomicMark(this, IS_ENABLED_UPDATER); + if (state.isNotEmpty()) { + /* + * If the initial state is already non-empty, trigger the predicate edge. + */ + AtomicUtils.atomicMark(this, PREDICATE_TRIGGERED_UPDATER); + } + } + } + + public void setPredicate(TypeFlow predicate) { + AtomicUtils.atomicSet(this, predicate, PREDICATE_UPDATER); + } + + public TypeFlow getPredicate() { + return PREDICATE_UPDATER.get(this); + } + + public boolean isPrimitiveFlow() { + return isPrimitiveFlow; + } + + /** + * Enables the flow and triggers value propagation if bb is not null. + *

+ * Some global flows are created and enabled early on in a place where a reference to bb is not + * easily available, so they pass in null, enabling the flow, but not triggering value + * propagation yet. + * + * @return true iff the flow was enabled by this operation + */ + public boolean enableFlow(PointsToAnalysis bb) { + if (AtomicUtils.atomicMark(this, IS_ENABLED_UPDATER)) { + if (bb != null) { + onFlowEnabled(bb); + if (inputSaturated) { + onInputSaturated(bb, null); + } + if (observedSaturated) { + onObservedSaturated(bb, null); + } + } + return true; + } + return false; + } + + /** + * Hook to perform any updates that should only be done once a flow is enabled by its predicate. + */ + protected void onFlowEnabled(PointsToAnalysis bb) { + if (state.isNotEmpty()) { + propagateState(bb, true, state); + } + } + + /** Add a new predicated flow. */ + public void addPredicated(PointsToAnalysis bb, TypeFlow predicatedFlow) { + predicatedFlow.setPredicate(this); + ConcurrentLightHashSet.addElement(this, PREDICATED_FLOWS_UPDATER, predicatedFlow); + if (predicateAlreadyTriggered()) { + predicatedFlow.enableFlow(bb); + /* + * The add-check-remove sequence is to prevent a data race with the enablePredicated + * method. + */ + ConcurrentLightHashSet.removeElement(this, PREDICATED_FLOWS_UPDATER, predicatedFlow); + } + } + + private void removePredicated(TypeFlow predicatedFlow) { + ConcurrentLightHashSet.removeElement(this, PREDICATED_FLOWS_UPDATER, predicatedFlow); + } + + public Collection> getPredicatedFlows() { + return ConcurrentLightHashSet.getElements(this, PREDICATED_FLOWS_UPDATER); + } + + public boolean predicateAlreadyTriggered() { + return AtomicUtils.isSet(this, PREDICATE_TRIGGERED_UPDATER); + } + + public final boolean isFlowEnabled() { + return AtomicUtils.isSet(this, IS_ENABLED_UPDATER); } private void validateSource() { @@ -171,6 +334,9 @@ public TypeFlow(TypeFlow original, MethodFlowsGraph graphRef) { public TypeFlow(TypeFlow original, MethodFlowsGraph graphRef, TypeState cloneState) { this(original.getSource(), original.getDeclaredType(), cloneState, original.getSlot(), true, graphRef); PointsToStats.registerTypeFlowRetainReason(this, original); + if (original.isFlowEnabled()) { + enableFlow(null); + } } /** @@ -251,6 +417,7 @@ public AnalysisType getDeclaredType() { } public TypeState getState() { + /* GR-58690 - We should not query the type state of disabled flows. */ return state; } @@ -285,7 +452,7 @@ public void invalidate() { * immediately remove itself from all its inputs. The inputs lazily remove it on next update. */ public boolean isSaturated() { - return isSaturated; + return AtomicUtils.isSet(this, SATURATED_UPDATER); } /** @@ -300,8 +467,9 @@ public boolean canSaturate() { * Mark this flow as saturated. Each flow starts with isSaturated as false and once it is set to * true it cannot be changed. */ - public void setSaturated() { - isSaturated = true; + public boolean setSaturated() { + assert isFlowEnabled() : "A flow cannot saturate before it is enabled."; + return AtomicUtils.atomicMark(this, SATURATED_UPDATER); } public boolean addState(PointsToAnalysis bb, TypeState add) { @@ -311,7 +479,6 @@ public boolean addState(PointsToAnalysis bb, TypeState add) { /* Add state and notify inputs of the result. */ public boolean addState(PointsToAnalysis bb, TypeState add, boolean postFlow) { PointsToStats.registerTypeFlowUpdate(bb, this, add); - TypeState before; TypeState after; TypeState filteredAdd; @@ -326,21 +493,35 @@ public boolean addState(PointsToAnalysis bb, TypeState add, boolean postFlow) { PointsToStats.registerTypeFlowSuccessfulUpdate(bb, this, add); - assert !bb.trackPrimitiveValues() || primitiveFlowCheck(after) : this + "," + after; - if (checkSaturated(bb, after)) { + assert !bb.trackPrimitiveValues() || primitiveFlowCheck(after) : after + ", " + this; + + /* Only propagate values if this flow is enabled */ + if (isFlowEnabled()) { + propagateState(bb, postFlow, after); + } + + return true; + } + + protected void propagateState(PointsToAnalysis bb, boolean postFlow, TypeState newState) { + assert isFlowEnabled() : "A flow cannot propagate state before it is enabled: " + this; + assert newState.isNotEmpty() : "Empty state should not trigger propagation: " + this; + enablePredicated(bb); + if (checkSaturated(bb, newState)) { onSaturated(bb); } else if (postFlow) { bb.postFlow(this); } - - return true; } - /** - * Primitive flows should only have primitive or empty type states. - */ - private boolean primitiveFlowCheck(TypeState newState) { - return !isPrimitiveFlow || newState.isPrimitive() || newState.isEmpty(); + /** Enables all the predicated flows and clears the predicatedFlows set. */ + protected void enablePredicated(PointsToAnalysis bb) { + if (AtomicUtils.atomicMark(this, PREDICATE_TRIGGERED_UPDATER)) { + ConcurrentLightHashSet.forEach(this, PREDICATED_FLOWS_UPDATER, (TypeFlow predicatedFlow) -> { + bb.postTask(() -> predicatedFlow.enableFlow(bb)); + }); + ConcurrentLightHashSet.clear(this, PREDICATED_FLOWS_UPDATER); + } } // manage uses @@ -368,6 +549,15 @@ private boolean checkDefUseCompatibility(TypeFlow use) { * FieldFilterTypeFlows. */ return true; + } else if (!this.isPrimitiveFlow && (use instanceof BooleanNullCheckTypeFlow || use instanceof BooleanInstanceOfCheckTypeFlow)) { + /* Valid transformation, converts an object into boolean. */ + return true; + } else if (use instanceof FormalReturnTypeFlow && use.getDeclaredType().getJavaKind() == JavaKind.Void) { + /* + * Object to primitive use edge from predicate to ReturnFlow is used to model the + * reachability of the return. + */ + return true; } return false; } @@ -397,7 +587,7 @@ public boolean addUse(PointsToAnalysis bb, TypeFlow use, boolean propagateTyp /* And unlink the use. */ removeUse(use); return false; - } else { + } else if (isFlowEnabled()) { use.addState(bb, getState()); } } @@ -466,7 +656,7 @@ public boolean addObserver(PointsToAnalysis bb, TypeFlow observer, boolean tr notifyObserverOfSaturation(bb, observer); removeObserver(observer); return false; - } else if (!this.state.isEmpty()) { + } else if (isFlowEnabled() && !this.state.isEmpty()) { /* Only trigger an observer update if this flow has a non-empty state. */ /* * Notify the observer after registering. This flow might have already reached a @@ -580,13 +770,20 @@ public TypeState declaredTypeFilter(PointsToAnalysis bb, TypeState newState, boo return newState; } if (isPrimitiveFlow) { - assert newState.isPrimitive() || newState.isEmpty() : newState + "," + this; + assert primitiveFlowCheck(newState) : newState + ", " + this; return newState; } /* By default, filter all type flows with the declared type. */ return TypeState.forIntersection(bb, newState, declaredType.getAssignableTypes(true)); } + /** + * Primitive flows should only have primitive or empty type states. + */ + private boolean primitiveFlowCheck(TypeState newState) { + return !isPrimitiveFlow || newState.isPrimitive() || newState.isEmpty(); + } + /** * In Java, interface types are not checked by the bytecode verifier. So even when, e.g., a * method parameter has the declared type Comparable, any Object can be passed in. We therefore @@ -648,7 +845,7 @@ boolean checkSaturated(PointsToAnalysis bb, TypeState typeState) { return false; } if (typeState.isPrimitive()) { - return !(typeState instanceof PrimitiveConstantTypeState); + return ((PrimitiveTypeState) typeState).isAnyPrimitive(); } return typeState.typesCount() > bb.analysisPolicy().typeFlowSaturationCutoff(); } @@ -657,6 +854,7 @@ boolean checkSaturated(PointsToAnalysis bb, TypeState typeState) { protected void onSaturated(PointsToAnalysis bb) { assert bb.analysisPolicy().removeSaturatedTypeFlows() : "The type flow saturation optimization is disabled."; assert canSaturate() : "This type flow cannot saturate."; + assert isFlowEnabled() : "A flow cannot saturate before it is enabled."; /* * Array type flow aliasing needs to be enabled for the type flow saturation optimization to * work correctly. When the receiver object of an array load/store operation is saturated, @@ -668,15 +866,16 @@ protected void onSaturated(PointsToAnalysis bb) { */ assert bb.analysisPolicy().aliasArrayTypeFlows() : "Array type flows must be aliased."; - if (isSaturated()) { + /* Mark the flow as saturated, this will lead to lazy removal from *all* its inputs. */ + if (!setSaturated()) { /* This flow is already marked as saturated. */ return; } - /* Mark the flow as saturated, this will lead to lazy removal from *all* its inputs. */ - setSaturated(); /* Run flow-specific saturation tasks, e.g., stop observing receivers. */ onSaturated(); + /* Enable predicated flows */ + enablePredicated(bb); /* Notify uses and observers that this input is saturated and unlink them. */ notifySaturated(bb); } @@ -705,6 +904,9 @@ protected void swapOut(PointsToAnalysis bb, TypeFlow newFlow) { for (TypeFlow observer : getObservers()) { swapAtObserver(bb, newFlow, observer); } + for (TypeFlow predicatedFlow : getPredicatedFlows()) { + swapAtPredicated(bb, newFlow, predicatedFlow); + } } protected void swapAtUse(PointsToAnalysis bb, TypeFlow newFlow, TypeFlow use) { @@ -718,6 +920,11 @@ protected void swapAtObserver(PointsToAnalysis bb, TypeFlow newFlow, TypeFlow observer.replacedObservedWith(bb, newFlow); } + private void swapAtPredicated(PointsToAnalysis bb, TypeFlow newFlow, TypeFlow predicatedFlow) { + removePredicated(predicatedFlow); + newFlow.addPredicated(bb, predicatedFlow); + } + /** * Notified by an input that it is saturated and it will stop sending updates. */ @@ -727,6 +934,14 @@ protected void onInputSaturated(PointsToAnalysis bb, @SuppressWarnings("unused") /* This type flow needs to track all its individual types. */ return; } + + if (!isFlowEnabled()) { + inputSaturated = true; + /* Another thread could enable the flow in the meantime, so check again. */ + if (!isFlowEnabled()) { + return; + } + } /* * By default when a type flow is notified that one of its inputs is saturated it will just * pass this information to its uses and observers and unlink them. Subclases should diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowBuilder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowBuilder.java index 32baacd12abb..977b052b3158 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowBuilder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowBuilder.java @@ -26,16 +26,20 @@ import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.function.Supplier; -import jdk.graal.compiler.phases.common.LazyValue; - import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.flow.AlwaysEnabledPredicateFlow; +import com.oracle.graal.pointsto.flow.PredicateMergeFlow; import com.oracle.graal.pointsto.flow.PrimitiveFlow; import com.oracle.graal.pointsto.flow.TypeFlow; +import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.typestate.PointsToStats; +import jdk.graal.compiler.phases.common.LazyValue; + /** * The type flow builder is a node in the type flow builder graph. The {@link #useDependencies} and * {@link #observerDependencies} are links to inputs from a data flow perspective, i.e., they are @@ -46,9 +50,10 @@ */ public final class TypeFlowBuilder> { - public static > TypeFlowBuilder create(PointsToAnalysis bb, Object source, Class clazz, Supplier supplier) { - TypeFlowBuilder builder = new TypeFlowBuilder<>(source, clazz, new LazyValue<>(supplier)); - assert checkForPrimitiveFlows(bb, clazz) : "Primitive flow encountered without -H:+TrackPrimitiveValues: " + clazz; + public static > TypeFlowBuilder create(PointsToAnalysis bb, PointsToAnalysisMethod method, Object predicate, Object source, Class clazz, Supplier supplier) { + TypeFlowBuilder builder = new TypeFlowBuilder<>(source, predicate, clazz, new LazyValue<>(supplier)); + assert checkForPrimitiveFlows(bb, clazz) : "Primitive flow encountered without -H:+TrackPrimitiveValues: " + clazz + " in method " + method.getQualifiedName(); + assert checkPredicate(bb, clazz, predicate) : "Null or invalid predicate " + predicate + " encountered with -H:+UsePredicates: " + clazz + " in method " + method.getQualifiedName(); PointsToStats.registerTypeFlowBuilder(bb, builder); return builder; } @@ -61,6 +66,25 @@ private static > boolean checkForPrimitiveFlows(PointsToAn return bb.trackPrimitiveValues() || !PrimitiveFlow.class.isAssignableFrom(clazz); } + /** + * A sanity check. If predicates are enabled, the predicate object should be: a) null for + * AlwaysEnabledPredicateFlow, b) List of TypeFlowBuilders for PredicateMergeFlow, c) + * TypeFlowBuilder otherwise. + */ + private static boolean checkPredicate(PointsToAnalysis bb, Class clazz, Object predicate) { + if (!bb.usePredicates()) { + assert predicate == null : "Predicates are disabled: " + predicate; + return true; + } + if (clazz == AlwaysEnabledPredicateFlow.class) { + return predicate == null; + } + if (clazz == PredicateMergeFlow.class) { + return predicate instanceof List; + } + return predicate instanceof TypeFlowBuilder; + } + private final Object source; private final Class flowClass; @@ -72,7 +96,14 @@ private static > boolean checkForPrimitiveFlows(PointsToAn private boolean buildingAnActualParameter; private boolean isMaterialized; - private TypeFlowBuilder(Object source, Class flowClass, LazyValue creator) { + /** + * Predicate dependency. Most often, it will be a single type flow builder, apart from + * PredicateMergeFlow, which has a list of type flow builders as predicates (one for each input + * branch), or AlwaysEnabledPredicateFlow, which has no predicate. + */ + private final Object predicate; + + private TypeFlowBuilder(Object source, Object predicate, Class flowClass, LazyValue creator) { this.flowClass = flowClass; this.source = source; this.lazyTypeFlowCreator = creator; @@ -80,6 +111,7 @@ private TypeFlowBuilder(Object source, Class flowClass, LazyValue creator) this.observerDependencies = new HashSet<>(); this.buildingAnActualParameter = false; this.isMaterialized = false; + this.predicate = predicate; } public void markAsBuildingAnActualParameter() { @@ -118,6 +150,10 @@ Collection> getObserverDependencies() { return observerDependencies; } + public Object getPredicate() { + return predicate; + } + public T get() { T value = lazyTypeFlowCreator.get(); isMaterialized = true; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowGraphBuilder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowGraphBuilder.java index ac60cfd48d86..87cc8b2aaef9 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowGraphBuilder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/builder/TypeFlowGraphBuilder.java @@ -33,6 +33,7 @@ import java.util.stream.Collectors; import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.flow.AlwaysEnabledPredicateFlow; import com.oracle.graal.pointsto.flow.TypeFlow; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.typestate.PointsToStats; @@ -147,6 +148,42 @@ public List> build() { /* Materialize the builder. */ TypeFlow flow = builder.get(); + var predicate = builder.getPredicate(); + if (predicate != null) { + assert bb.usePredicates() : "Predicates should only be used with -H:+UsePredicates."; + if (predicate instanceof TypeFlowBuilder singlePredicate) { + singlePredicate.get().addPredicated(bb, flow); + if (!processed.contains(singlePredicate)) { + workQueue.addLast(singlePredicate); + } + } else { + @SuppressWarnings("unchecked") + var predicateList = ((List>) predicate); + for (TypeFlowBuilder p : predicateList) { + p.get().addPredicated(bb, flow); + if (!processed.contains(p)) { + workQueue.addLast(p); + } + } + } + } else { + assert !bb.usePredicates() || flow instanceof AlwaysEnabledPredicateFlow : "Flow " + flow + " does not have a predicate."; + /* + * If there is no predicate, enable the flow immediately. However, we only want + * to propagate updates in the context-insensitive analysis. In the + * context-sensitive analysis, the original graph is used for cloning only, so + * we do not want to send any updates through it, hence we pass null for bb. + */ + flow.enableFlow(bb.analysisPolicy().isContextSensitiveAnalysis() ? null : bb); + } + if (bb.isBaseLayerAnalysisEnabled()) { + /* + * GR-58387 - Currently, we force enable all the flows in the base layer, which + * is a workaround that should eventually be removed. + */ + flow.enableFlow(bb.analysisPolicy().isContextSensitiveAnalysis() ? null : bb); + } + if (flow.needsInitialization()) { postInitFlows.add(flow); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveStaticInvokeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveStaticInvokeTypeFlow.java index 0546fa606bad..cf735c99b907 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveStaticInvokeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveStaticInvokeTypeFlow.java @@ -78,9 +78,23 @@ public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph met return new BytecodeSensitiveStaticInvokeTypeFlow(bb, methodFlows, this); } + @Override + public boolean needsInitialization() { + return true; + } + + @Override + public void initFlow(PointsToAnalysis bb) { + /* Trigger the update for static invokes, there is no receiver to trigger it. */ + if (isClone() && isFlowEnabled()) { + bb.postFlow(this); + } + } + @Override public void update(PointsToAnalysis bb) { - assert this.isClone() : this; + assert isFlowEnabled() : "The linking should only be triggered for enabled flows: " + this; + assert isClone() : "Only clones should be updated: " + this; /* The static invokes should be updated only once and the callee should be null. */ guarantee(LightImmutableCollection.isEmpty(this, CALLEES_ACCESSOR), "static invoke updated multiple times!"); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveVirtualInvokeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveVirtualInvokeTypeFlow.java index af838df6692e..1df37dda85f0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveVirtualInvokeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/context/bytecode/BytecodeSensitiveVirtualInvokeTypeFlow.java @@ -80,6 +80,13 @@ public TypeFlow copy(PointsToAnalysis bb, MethodFlowsGraph met return new BytecodeSensitiveVirtualInvokeTypeFlow(bb, methodFlows, this); } + @Override + protected void onFlowEnabled(PointsToAnalysis bb) { + if (isClone()) { + bb.postTask(() -> onObservedUpdate(bb)); + } + } + @Override public void onObservedUpdate(PointsToAnalysis bb) { assert this.isClone() || this.isContextInsensitive() : this; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisField.java index e351e6da663e..bf82bbe57f7e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisField.java @@ -62,6 +62,7 @@ private StoreInstanceFieldTypeFlow createContextInsensitiveStore(PointsToAnalysi */ StoreInstanceFieldTypeFlow store = new StoreInstanceFieldTypeFlow(originalLocation, this, objectFlow); store.markAsContextInsensitive(); + store.enableFlow(bb); return store; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisMethod.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisMethod.java index d011e45e7e03..c6bd793ac246 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisMethod.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisMethod.java @@ -42,6 +42,7 @@ import com.oracle.svm.common.meta.MultiMethod; import jdk.vm.ci.code.BytecodePosition; +import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaMethod; public final class PointsToAnalysisMethod extends AnalysisMethod { @@ -196,11 +197,13 @@ private static InvokeTypeFlow createContextInsensitiveInvoke(PointsToAnalysis bb actualParameters[0] = receiverFlow; for (int i = 1; i < actualParameters.length; i++) { actualParameters[i] = new ActualParameterTypeFlow(method.getSignature().getParameterType(i - 1)); + actualParameters[i].enableFlow(bb); } ActualReturnTypeFlow actualReturn = null; AnalysisType returnType = method.getSignature().getReturnType(); - if (bb.isSupportedJavaKind(returnType.getStorageKind())) { + if (bb.isSupportedJavaKind(returnType.getStorageKind()) || (bb.usePredicates() && returnType.getStorageKind() == JavaKind.Void)) { actualReturn = new ActualReturnTypeFlow(returnType); + actualReturn.enableFlow(bb); } InvokeTypeFlow invoke; @@ -211,6 +214,7 @@ private static InvokeTypeFlow createContextInsensitiveInvoke(PointsToAnalysis bb invoke = bb.analysisPolicy().createVirtualInvokeTypeFlow(originalLocation, receiverType, method, actualParameters, actualReturn, callerMultiMethodKey); } + invoke.enableFlow(bb); invoke.markAsContextInsensitive(); return invoke; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java index 6f035f0cb98c..4e0c7b99e11f 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisType.java @@ -103,6 +103,7 @@ private UnsafeStoreTypeFlow createContextInsensitiveUnsafeStore(PointsToAnalysis */ UnsafeStoreTypeFlow store = new UnsafeStoreTypeFlow(originalLocation, this, componentType, objectFlow, null); store.markAsContextInsensitive(); + store.enableFlow(bb); return store; } @@ -133,6 +134,7 @@ private StoreIndexedTypeFlow createContextInsensitiveIndexedStore(PointsToAnalys */ StoreIndexedTypeFlow store = new StoreIndexedTypeFlow(originalLocation, this, objectFlow, null); store.markAsContextInsensitive(); + store.enableFlow(bb); return store; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java index 8e66f3d6458e..8fa695ec560a 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java @@ -45,6 +45,7 @@ import com.oracle.graal.pointsto.flow.InvokeTypeFlow; import com.oracle.graal.pointsto.flow.MethodFlowsGraph; import com.oracle.graal.pointsto.flow.MethodTypeFlow; +import com.oracle.graal.pointsto.flow.PrimitiveFilterTypeFlow; import com.oracle.graal.pointsto.flow.TypeFlow; import com.oracle.graal.pointsto.heap.ImageHeapConstant; import com.oracle.graal.pointsto.infrastructure.Universe; @@ -94,6 +95,8 @@ import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.calc.ConditionalNode; +import jdk.graal.compiler.nodes.calc.IntegerEqualsNode; +import jdk.graal.compiler.nodes.calc.IntegerLowerThanNode; import jdk.graal.compiler.nodes.calc.IsNullNode; import jdk.graal.compiler.nodes.extended.BytecodeExceptionNode; import jdk.graal.compiler.nodes.extended.FieldOffsetProvider; @@ -459,7 +462,7 @@ public void simplify(Node n, SimplifierTool tool) { boolean falseUnreachable = isUnreachable(node.falseSuccessor()); if (trueUnreachable && falseUnreachable) { - makeUnreachable(node, tool, () -> "method " + graph.method().format("%H.%n(%p)") + ", node " + node + ": both successors of IfNode are unreachable"); + makeUnreachable(node, tool, () -> "method " + getQualifiedName(graph) + ", node " + node + ": both successors of IfNode are unreachable"); } else if (trueUnreachable || falseUnreachable) { AbstractBeginNode killedBegin = node.successor(trueUnreachable); @@ -589,6 +592,7 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) { /* Invoke is unreachable, there is no point in improving any types further. */ return; } + assert invokeFlow.isFlowEnabled() : "Disabled invoke should have no callees: " + invokeFlow + ", in method " + getQualifiedName(graph); FixedWithNextNode beforeInvoke = (FixedWithNextNode) invoke.predecessor(); NodeInputList arguments = callTarget.arguments(); @@ -637,7 +641,7 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) { * interface call may have already been optimized to a special call by stamp * strengthening of the receiver object, hence the invoke kind is direct, whereas * the points-to analysis inaccurately concluded there can be more than one callee. - * + * * Below we just check that if there is a direct invoke *and* the analysis * discovered a single callee, then the callee should match the target method. */ @@ -684,8 +688,10 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) { */ JavaMethodProfile methodProfile = makeMethodProfile(callees); - assert typeProfile == null || typeProfile.getTypes().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile; - assert methodProfile == null || methodProfile.getMethods().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile; + assert typeProfile == null || typeProfile.getTypes().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile + " and callees" + + callees + " invoke " + invokeFlow + " " + invokeFlow.getReceiver() + " in method " + getQualifiedName(graph); + assert methodProfile == null || methodProfile.getMethods().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile + + " and callees" + callees + " invoke " + invokeFlow + " " + invokeFlow.getReceiver() + " in method " + getQualifiedName(graph); setInvokeProfiles(invoke, typeProfile, methodProfile); } @@ -696,7 +702,19 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) { } FixedWithNextNode anchorPointAfterInvoke = (FixedWithNextNode) (invoke instanceof InvokeWithExceptionNode ? invoke.next() : invoke); - Object newStampOrConstant = strengthenStampFromTypeFlow(node, invokeFlow.getResult(), anchorPointAfterInvoke, tool); + TypeFlow nodeFlow = invokeFlow.getResult(); + if (nodeFlow != null && node.getStackKind() == JavaKind.Void && !methodFlow.isSaturated((PointsToAnalysis) bb, nodeFlow)) { + /* + * We track the reachability of return statements in void methods via returning + * either Empty or AnyPrimitive TypeState, therefore we perform an emptiness check. + */ + var typeState = methodFlow.foldTypeFlow((PointsToAnalysis) bb, nodeFlow); + if (typeState.isEmpty() && unreachableValues.add(node)) { + makeUnreachable(anchorPointAfterInvoke.next(), tool, + () -> "method " + getQualifiedName(graph) + ", node " + node + ": return from void method was proven unreachable"); + } + } + Object newStampOrConstant = strengthenStampFromTypeFlow(node, nodeFlow, anchorPointAfterInvoke, tool); updateStampUsingPiNode(node, newStampOrConstant, anchorPointAfterInvoke, tool); } @@ -758,7 +776,7 @@ private void unreachableInvoke(Invoke invoke, SimplifierTool tool) { InliningUtil.nonNullReceiver(invoke); } - makeUnreachable(invoke.asFixedNode(), tool, () -> "method " + ((AnalysisMethod) graph.method()).getQualifiedName() + ", node " + invoke + + makeUnreachable(invoke.asFixedNode(), tool, () -> "method " + getQualifiedName(graph) + ", node " + invoke + ": empty list of callees for call to " + ((AnalysisMethod) invoke.callTarget().targetMethod()).getQualifiedName()); } @@ -784,9 +802,18 @@ private void devirtualizeInvoke(AnalysisMethod singleCallee, Invoke invoke) { private boolean isUnreachable(Node branch) { TypeFlow branchFlow = getNodeFlow(branch); - return branchFlow != null && - !methodFlow.isSaturated((PointsToAnalysis) bb, branchFlow) && - methodFlow.foldTypeFlow((PointsToAnalysis) bb, branchFlow).isEmpty(); + if (branchFlow != null && !methodFlow.isSaturated(((PointsToAnalysis) bb), branchFlow)) { + TypeState typeState = methodFlow.foldTypeFlow((PointsToAnalysis) bb, branchFlow); + if (branchFlow.isPrimitiveFlow()) { + /* + * This assert is a safeguard to verify the assumption that only one type of + * flow has to be considered as a branch predicate at the moment. + */ + assert branchFlow instanceof PrimitiveFilterTypeFlow : "Unexpected type of primitive flow encountered as branch predicate: " + branchFlow; + } + return !branchFlow.isFlowEnabled() || typeState.isEmpty(); + } + return false; } private void updateStampInPlace(ValueNode node, Stamp newStamp, SimplifierTool tool) { @@ -878,7 +905,7 @@ private Object strengthenStampFromTypeFlow(ValueNode node, TypeFlow nodeFlow, node.inferStamp(); Stamp s = node.stamp(NodeView.DEFAULT); if (s.isIntegerStamp() || nodeTypeState.isPrimitive()) { - return getIntegerStamp(node, s, nodeTypeState); + return getIntegerStamp(node, ((IntegerStamp) s), anchorPoint, nodeTypeState, tool); } ObjectStamp oldStamp = (ObjectStamp) s; @@ -901,7 +928,7 @@ private Object strengthenStampFromTypeFlow(ValueNode node, TypeFlow nodeFlow, if (typeStateTypes.size() == 0) { if (nonNull) { makeUnreachable(anchorPoint.next(), tool, - () -> "method " + ((AnalysisMethod) graph.method()).getQualifiedName() + ", node " + node + ": empty stamp when strengthening oldStamp " + oldStamp); + () -> "method " + getQualifiedName(graph) + ", node " + node + ": empty object type state when strengthening oldStamp " + oldStamp); unreachableValues.add(node); return null; } else { @@ -972,9 +999,15 @@ assert getSingleImplementorType(baseType) == null || baseType.equals(getSingleIm return null; } - private IntegerStamp getIntegerStamp(ValueNode node, Stamp stamp, TypeState nodeTypeState) { + private IntegerStamp getIntegerStamp(ValueNode node, IntegerStamp originalStamp, FixedWithNextNode anchorPoint, TypeState nodeTypeState, SimplifierTool tool) { assert bb.trackPrimitiveValues() : nodeTypeState + "," + node + " in " + node.graph(); assert nodeTypeState != null && (nodeTypeState.isEmpty() || nodeTypeState.isPrimitive()) : nodeTypeState + "," + node + " in " + node.graph(); + if (nodeTypeState.isEmpty()) { + makeUnreachable(anchorPoint.next(), tool, + () -> "method " + getQualifiedName(graph) + ", node " + node + ": empty primitive type state when strengthening oldStamp " + originalStamp); + unreachableValues.add(node); + return null; + } if (nodeTypeState instanceof PrimitiveConstantTypeState constantTypeState) { long constantValue = constantTypeState.getValue(); if (node instanceof ConstantNode constant) { @@ -987,7 +1020,7 @@ private IntegerStamp getIntegerStamp(ValueNode node, Stamp stamp, TypeState node assert ((PrimitiveConstant) value).asLong() == constantValue : "The actual value of node: " + value + " is different than the value " + constantValue + " computed by points-to analysis, method in " + node.graph().method(); } else { - return IntegerStamp.createConstant(((IntegerStamp) stamp).getBits(), constantValue); + return IntegerStamp.createConstant(originalStamp.getBits(), constantValue); } } return null; @@ -1061,6 +1094,10 @@ private Stamp strengthenStamp(Stamp s) { } } + private static String getQualifiedName(StructuredGraph graph) { + return ((AnalysisMethod) graph.method()).getQualifiedName(); + } + protected JavaTypeProfile makeTypeProfile(TypeState typeState) { if (typeState == null || analysisSizeCutoff != -1 && typeState.typesCount() > analysisSizeCutoff) { return null; @@ -1119,6 +1156,7 @@ enum Counter { BLOCK, IS_NULL, INSTANCE_OF, + PRIM_CMP, INVOKE_STATIC, INVOKE_DIRECT, INVOKE_INDIRECT, @@ -1167,6 +1205,8 @@ private static void collect(int[] localValues, LogicNode condition) { inc(localValues, Counter.IS_NULL); } else if (condition instanceof InstanceOfNode) { inc(localValues, Counter.INSTANCE_OF); + } else if (condition instanceof IntegerEqualsNode || condition instanceof IntegerLowerThanNode) { + inc(localValues, Counter.PRIM_CMP); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/AnyPrimitiveTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/AnyPrimitiveTypeState.java index c359378ff1c5..0168f5b7ad04 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/AnyPrimitiveTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/AnyPrimitiveTypeState.java @@ -24,14 +24,6 @@ */ package com.oracle.graal.pointsto.typestate; -import java.util.Iterator; - -import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.PointsToAnalysis; -import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.util.AnalysisError; - /** * Represents 'any' primitive value - a value, about which we do not maintain any useful * information. @@ -48,69 +40,30 @@ *
* When a type flow receives this state, it leads to immediate saturation. */ -public class AnyPrimitiveTypeState extends TypeState { +public final class AnyPrimitiveTypeState extends PrimitiveTypeState { static final AnyPrimitiveTypeState SINGLETON = new AnyPrimitiveTypeState(); - protected AnyPrimitiveTypeState() { - } - - private static RuntimeException shouldNotReachHere() { - throw AnalysisError.shouldNotReachHere("This method should never be called."); - } - - @Override - public int typesCount() { - throw shouldNotReachHere(); - } - - @Override - public AnalysisType exactType() { - throw shouldNotReachHere(); - } - - @Override - protected Iterator typesIterator(BigBang bb) { - throw shouldNotReachHere(); - } - - @Override - public boolean containsType(AnalysisType exactType) { - throw shouldNotReachHere(); + private AnyPrimitiveTypeState() { } @Override - public int objectsCount() { - throw shouldNotReachHere(); - } - - @Override - protected Iterator objectsIterator(BigBang bb) { - throw shouldNotReachHere(); - } - - @Override - protected Iterator objectsIterator(AnalysisType type) { - throw shouldNotReachHere(); - } - - @Override - public boolean canBeNull() { - return false; + public String toString() { + return "AnyPrimitiveTypeState"; } @Override - public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { - throw shouldNotReachHere(); + public boolean equals(Object o) { + return this == o; } @Override - public String toString() { - return "PrimitiveTypeState"; + public boolean canBeFalse() { + return true; } @Override - public boolean equals(Object o) { - return this == o; + public boolean canBeTrue() { + return true; } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultSpecialInvokeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultSpecialInvokeTypeFlow.java index 0b6a129a1617..6e4a13530f37 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultSpecialInvokeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultSpecialInvokeTypeFlow.java @@ -58,6 +58,9 @@ final class DefaultSpecialInvokeTypeFlow extends AbstractSpecialInvokeTypeFlow { @Override public void onObservedUpdate(PointsToAnalysis bb) { assert !isSaturated() : this; + if (!isFlowEnabled()) { + return; + } /* * Filter types not compatible with the receiver type and determine which types have been diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultStaticInvokeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultStaticInvokeTypeFlow.java index 2d748a2b7846..1bf8a47b5cfc 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultStaticInvokeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultStaticInvokeTypeFlow.java @@ -55,10 +55,16 @@ final class DefaultStaticInvokeTypeFlow extends AbstractStaticInvokeTypeFlow { this.isDeoptInvokeTypeFlow = isDeoptInvokeTypeFlow; } + @Override + protected void onFlowEnabled(PointsToAnalysis bb) { + bb.postTask(() -> update(bb)); + } + @Override public void update(PointsToAnalysis bb) { + assert isFlowEnabled() : "The linking should only be triggered for enabled flows: " + this; /* The static invokes should be updated only once and the callee should be null. */ - guarantee(LightImmutableCollection.isEmpty(this, CALLEES_ACCESSOR), "static invoke updated multiple times!"); + guarantee(LightImmutableCollection.isEmpty(this, CALLEES_ACCESSOR), "Static invoke updated multiple times, source %s, target method %s", getSource(), targetMethod); // Unlinked methods can not be parsed if (!targetMethod.getWrapped().getDeclaringClass().isLinked()) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultVirtualInvokeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultVirtualInvokeTypeFlow.java index 5b4516829356..7ec656a7f1b8 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultVirtualInvokeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/DefaultVirtualInvokeTypeFlow.java @@ -56,6 +56,9 @@ final class DefaultVirtualInvokeTypeFlow extends AbstractVirtualInvokeTypeFlow { @Override public void onObservedUpdate(PointsToAnalysis bb) { + if (!isFlowEnabled()) { + return; + } if (isSaturated()) { /* The receiver can saturate while the invoke update was waiting to be scheduled. */ return; @@ -70,6 +73,7 @@ public void onObservedUpdate(PointsToAnalysis bb) { } for (AnalysisType type : receiverState.types(bb)) { + assert receiverType.isAssignableFrom(type) : type + " should be a subtype of " + receiverType; if (isSaturated()) { /*- * The receiver can become saturated during the callees linking, which saturates @@ -134,12 +138,22 @@ public void onObservedUpdate(PointsToAnalysis bb) { @Override public void onObservedSaturated(PointsToAnalysis bb, TypeFlow observed) { + if (!isFlowEnabled()) { + observedSaturated = true; + /* Another thread could enable the flow in the meantime, so check again. */ + if (!isFlowEnabled()) { + return; + } + } + + if (!setSaturated()) { + return; + } + /* Eagerly ensure context insensitive invoke is created before the saturated flag is set. */ AbstractVirtualInvokeTypeFlow contextInsensitiveInvoke = (AbstractVirtualInvokeTypeFlow) targetMethod.initAndGetContextInsensitiveInvoke(bb, source, false, callerMultiMethodKey); contextInsensitiveInvoke.addInvokeLocation(getSource()); - setSaturated(); - /* * The receiver object flow of the invoke operation is saturated; it will stop sending * notifications. Swap the invoke flow with the unique, context-insensitive invoke flow @@ -174,7 +188,6 @@ public void onObservedSaturated(PointsToAnalysis bb, TypeFlow observed) { * receiver is already set in the saturated invoke. */ for (int i = 1; i < actualParameters.length; i++) { - /* Primitive type parameters are not modeled, hence null. */ if (actualParameters[i] != null) { actualParameters[i].addUse(bb, contextInsensitiveInvoke.getActualParameter(i)); } @@ -186,15 +199,16 @@ public void onObservedSaturated(PointsToAnalysis bb, TypeFlow observed) { } @Override - public void setSaturated() { - super.setSaturated(); + public boolean setSaturated() { + var success = super.setSaturated(); if (this.isClone()) { /* * If this is a clone, mark the original as saturated too such that * originalInvoke.getCallees() is redirected to the context-insensitive invoke. */ - originalInvoke.setSaturated(); + success |= originalInvoke.setSaturated(); } + return success; } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java index ab5c0e923b55..862e87623aad 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveConstantTypeState.java @@ -31,7 +31,7 @@ * corresponding primitive values are accessible via a factory method * {@link TypeState#forPrimitiveConstant }. */ -public final class PrimitiveConstantTypeState extends AnyPrimitiveTypeState { +public final class PrimitiveConstantTypeState extends PrimitiveTypeState { private static final int CACHE_SIZE = 16; @@ -60,6 +60,16 @@ public long getValue() { return value; } + @Override + public boolean canBeTrue() { + return value != 0; + } + + @Override + public boolean canBeFalse() { + return value == 0; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveTypeState.java new file mode 100644 index 000000000000..52df6689c1e0 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/PrimitiveTypeState.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.typestate; + +import java.util.Iterator; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.flow.PrimitiveComparison; +import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.AnalysisError; + +/** + * Represents type state for primitive values. These type states can be either concrete constants + * represented by {@link PrimitiveConstantTypeState} or {@link AnyPrimitiveTypeState} that represent + * any primitive value and lead to immediate saturation. + */ +public abstract sealed class PrimitiveTypeState extends TypeState permits PrimitiveConstantTypeState, AnyPrimitiveTypeState { + + protected PrimitiveTypeState() { + } + + @Override + public final boolean isPrimitive() { + return true; + } + + @Override + public final boolean canBeNull() { + return false; + } + + @Override + public boolean isEmpty() { + return false; + } + + public boolean isAnyPrimitive() { + return this instanceof AnyPrimitiveTypeState; + } + + public TypeState forUnion(PrimitiveTypeState right) { + if (this instanceof AnyPrimitiveTypeState || right instanceof AnyPrimitiveTypeState) { + return AnyPrimitiveTypeState.SINGLETON; + } + if (this instanceof PrimitiveConstantTypeState c1 && right instanceof PrimitiveConstantTypeState c2 && c1.getValue() == c2.getValue()) { + return this; + } + return AnyPrimitiveTypeState.SINGLETON; + } + + /** Returns a type state filtered with respect to the comparison and right. */ + public TypeState filter(PrimitiveComparison comparison, PrimitiveTypeState right) { + return switch (comparison) { + case EQ -> forEquals(right); + case NEQ -> forNotEquals(right); + case LT -> forLessThan(right); + case GE -> forGreaterOrEqual(right); + case GT -> forGreaterThan(right); + case LE -> forLessOrEqual(right); + }; + } + + public TypeState forEquals(PrimitiveTypeState right) { + if (this instanceof PrimitiveConstantTypeState thisConstant) { + if (right instanceof PrimitiveConstantTypeState rightConstant && thisConstant.getValue() != rightConstant.getValue()) { + return forEmpty(); + } + return this; + } else if (this instanceof AnyPrimitiveTypeState) { + if (right instanceof PrimitiveConstantTypeState) { + return right; + } else { + return this; + } + } + throw AnalysisError.shouldNotReachHere("Combination not covered, this=" + this + ". right=" + right); + } + + public TypeState forNotEquals(PrimitiveTypeState right) { + if (this instanceof PrimitiveConstantTypeState thisConstant && right instanceof PrimitiveConstantTypeState rightConstant && thisConstant.getValue() == rightConstant.getValue()) { + return forEmpty(); + } + return this; + } + + public TypeState forLessThan(PrimitiveTypeState right) { + if (this instanceof PrimitiveConstantTypeState thisConstant && right instanceof PrimitiveConstantTypeState rightConstant && thisConstant.getValue() >= rightConstant.getValue()) { + return forEmpty(); + } + return this; + } + + public TypeState forGreaterOrEqual(PrimitiveTypeState right) { + if (this instanceof PrimitiveConstantTypeState thisConstant && right instanceof PrimitiveConstantTypeState rightConstant && thisConstant.getValue() < rightConstant.getValue()) { + return forEmpty(); + } + return this; + } + + public TypeState forLessOrEqual(PrimitiveTypeState right) { + if (this instanceof PrimitiveConstantTypeState thisConstant && right instanceof PrimitiveConstantTypeState rightConstant && thisConstant.getValue() > rightConstant.getValue()) { + return forEmpty(); + } + return this; + } + + public TypeState forGreaterThan(PrimitiveTypeState right) { + if (this instanceof PrimitiveConstantTypeState thisConstant && right instanceof PrimitiveConstantTypeState rightConstant && thisConstant.getValue() <= rightConstant.getValue()) { + return forEmpty(); + } + return this; + } + + public abstract boolean canBeTrue(); + + public abstract boolean canBeFalse(); + + @Override + public abstract String toString(); + + @Override + public abstract boolean equals(Object o); + + @Override + public int typesCount() { + throw shouldNotReachHere(); + } + + @Override + public AnalysisType exactType() { + throw shouldNotReachHere(); + } + + @Override + protected Iterator typesIterator(BigBang bb) { + throw shouldNotReachHere(); + } + + @Override + public boolean containsType(AnalysisType exactType) { + throw shouldNotReachHere(); + } + + @Override + public int objectsCount() { + throw shouldNotReachHere(); + } + + @Override + protected Iterator objectsIterator(BigBang bb) { + throw shouldNotReachHere(); + } + + @Override + protected Iterator objectsIterator(AnalysisType type) { + throw shouldNotReachHere(); + } + + @Override + public TypeState forCanBeNull(PointsToAnalysis bb, boolean stateCanBeNull) { + throw shouldNotReachHere(); + } + + private static RuntimeException shouldNotReachHere() { + throw AnalysisError.shouldNotReachHere("This method should never be called."); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java index 4fc5278b4dd3..c5b95038ac30 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeState.java @@ -32,6 +32,7 @@ import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.flow.PrimitiveComparison; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.meta.AnalysisType; @@ -109,12 +110,16 @@ public boolean isEmpty() { return this == EmptyTypeState.SINGLETON; } + public final boolean isNotEmpty() { + return !isEmpty(); + } + public boolean isNull() { return this == NullTypeState.SINGLETON; } public boolean isPrimitive() { - return this instanceof AnyPrimitiveTypeState; + return false; } public abstract boolean canBeNull(); @@ -157,6 +162,10 @@ public static TypeState forPrimitiveConstant(long value) { return PrimitiveConstantTypeState.forValue(value); } + public static TypeState forBoolean(boolean value) { + return value ? TypeState.forPrimitiveConstant(1) : TypeState.forPrimitiveConstant(0); + } + public static TypeState anyPrimitiveState() { return AnyPrimitiveTypeState.SINGLETON; } @@ -206,14 +215,9 @@ public static TypeState forUnion(PointsToAnalysis bb, TypeState s1, TypeState s2 return s1; } else if (s2.isNull()) { return s1.forCanBeNull(bb, true); - } else if (s1 instanceof PrimitiveConstantTypeState c1 && s2 instanceof PrimitiveConstantTypeState c2 && c1.getValue() == c2.getValue()) { - return s1; - } else if (s1.isPrimitive()) { - assert s2.isPrimitive() : s2; - return TypeState.anyPrimitiveState(); - } else if (s2.isPrimitive()) { - assert s1.isPrimitive() : s1; - return TypeState.anyPrimitiveState(); + } else if (s1.isPrimitive() || s2.isPrimitive()) { + assert s1 instanceof PrimitiveTypeState && s2 instanceof PrimitiveTypeState : "Both type states should be primitive: " + s1 + " " + s2; + return ((PrimitiveTypeState) s1).forUnion((PrimitiveTypeState) s2); } else if (s1 instanceof SingleTypeState && s2 instanceof SingleTypeState) { return bb.analysisPolicy().doUnion(bb, (SingleTypeState) s1, (SingleTypeState) s2); } else if (s1 instanceof SingleTypeState && s2 instanceof MultiTypeState) { @@ -268,6 +272,16 @@ public static TypeState forSubtraction(PointsToAnalysis bb, TypeState s1, TypeSt return bb.analysisPolicy().doSubtraction(bb, (MultiTypeState) s1, (MultiTypeState) s2); } } + + /** Returns a type state representing left filtered with respect to the comparison and right. */ + public static TypeState filter(TypeState left, PrimitiveComparison comparison, TypeState right) { + assert left.isPrimitive() || left.isEmpty() : left; + assert right.isPrimitive() || right.isEmpty() : right; + if (left.isEmpty() || right.isEmpty()) { + return forEmpty(); + } + return ((PrimitiveTypeState) left).filter(comparison, (PrimitiveTypeState) right); + } } final class EmptyTypeState extends TypeState { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeStateUtils.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeStateUtils.java index cf666c22621e..ab2a88321383 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeStateUtils.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/TypeStateUtils.java @@ -403,7 +403,7 @@ private static BitSet getClone(BitSet original) { } public static boolean closeToAllInstantiated(BigBang bb, TypeState state) { - if (state instanceof AnyPrimitiveTypeState) { + if (state.isPrimitive()) { return false; } return closeToAllInstantiated(bb, state.typesCount()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 3fd618db5886..2e0d358f289d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -1237,7 +1237,7 @@ public static void performSnippetGraphAnalysis(BigBang bb, SubstrateReplacements Collection snippetGraphs = replacements.getSnippetGraphs(GraalOptions.TrackNodeSourcePosition.getValue(options), options, objectTransformer); if (bb instanceof NativeImagePointsToAnalysis pointsToAnalysis) { for (StructuredGraph graph : snippetGraphs) { - MethodTypeFlowBuilder.registerUsedElements(pointsToAnalysis, graph); + MethodTypeFlowBuilder.registerUsedElements(pointsToAnalysis, graph, false); } } else if (bb instanceof NativeImageReachabilityAnalysisEngine reachabilityAnalysis) { for (StructuredGraph graph : snippetGraphs) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java index 1318134cc529..e391d4fb58ff 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java @@ -163,16 +163,16 @@ protected boolean delegateNodeProcessing(FixedNode n, TypeFlowsOfNodes state) { private void storeVMThreadLocal(TypeFlowsOfNodes state, ValueNode storeNode, ValueNode value) { Stamp stamp = value.stamp(NodeView.DEFAULT); - if (stamp instanceof ObjectStamp) { + if (stamp instanceof ObjectStamp valueStamp) { /* Add the value object to the state of its declared type. */ TypeFlowBuilder valueBuilder = state.lookup(value); - ObjectStamp valueStamp = (ObjectStamp) stamp; AnalysisType valueType = (AnalysisType) (valueStamp.type() == null ? bb.getObjectType() : valueStamp.type()); - TypeFlowBuilder storeBuilder = TypeFlowBuilder.create(bb, storeNode, TypeFlow.class, () -> { + TypeFlowBuilder predicate = state.getPredicate(); + TypeFlowBuilder storeBuilder = TypeFlowBuilder.create(bb, method, predicate, storeNode, TypeFlow.class, () -> { TypeFlow proxy = bb.analysisPolicy().proxy(AbstractAnalysisEngine.sourcePosition(storeNode), valueType.getTypeFlow(bb, false)); flowsGraph.addMiscEntryFlow(proxy); - return proxy; + return maybePatchAllInstantiated(proxy, valueType, predicate); }); storeBuilder.addUseDependency(valueBuilder); typeFlowGraphBuilder.registerSinkBuilder(storeBuilder); @@ -195,7 +195,7 @@ private void processLoadImageSingleton(TypeFlowsOfNodes state, LoadImageSingleto * AllTypesInstantiated flow for the returned type. */ AnalysisType singletonType = (AnalysisType) ((ObjectStamp) node.stamp(NodeView.DEFAULT)).type(); - var singletonTypeFlow = TypeFlowBuilder.create(bb, node, AllInstantiatedTypeFlow.class, () -> { + var singletonTypeFlow = TypeFlowBuilder.create(bb, method, state.getPredicate(), node, AllInstantiatedTypeFlow.class, () -> { singletonType.registerAsInstantiated(AbstractAnalysisEngine.sourcePosition(node)); return ((AllInstantiatedTypeFlow) singletonType.getTypeFlow(bb, false)); });