From d613cfd393ebc8e1a3f126bc49b52490e268247f Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Wed, 11 Aug 2021 15:06:45 -0700 Subject: [PATCH 1/2] Propagate assignable types eagerly. wip --- .../graal/pointsto/AnalysisObjectScanner.java | 42 +- .../com/oracle/graal/pointsto/BigBang.java | 15 +- .../oracle/graal/pointsto/ObjectScanner.java | 1 + .../pointsto/flow/AccessFieldTypeFlow.java | 2 +- .../flow/AllInstantiatedTypeFlow.java | 4 +- .../pointsto/flow/ArrayElementsTypeFlow.java | 4 +- .../pointsto/flow/FieldFilterTypeFlow.java | 2 +- .../graal/pointsto/flow/FieldTypeFlow.java | 4 +- .../graal/pointsto/flow/FilterTypeFlow.java | 6 +- .../pointsto/flow/FormalReceiverTypeFlow.java | 24 +- .../flow/InitialReceiverTypeFlow.java | 14 + .../graal/pointsto/flow/InvokeTypeFlow.java | 2 +- .../pointsto/flow/OffsetLoadTypeFlow.java | 2 +- .../pointsto/flow/OffsetStoreTypeFlow.java | 2 +- .../oracle/graal/pointsto/flow/TypeFlow.java | 20 +- .../graal/pointsto/meta/AnalysisType.java | 388 +++++++----------- .../graal/pointsto/meta/AnalysisUniverse.java | 15 + .../pointsto/typestate/MultiTypeState.java | 11 +- .../pointsto/typestate/SingleTypeState.java | 9 +- .../graal/pointsto/typestate/TypeState.java | 36 +- .../thread/Target_java_lang_ThreadGroup.java | 15 +- .../svm/hosted/NativeImageGenerator.java | 27 +- .../AnnotationSubstitutionProcessor.java | 2 + 23 files changed, 301 insertions(+), 346 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanner.java index 7b4a45addc7f..17d2add4fe01 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanner.java @@ -61,21 +61,13 @@ public void forNonNullFieldValue(JavaConstant receiver, AnalysisField field, Jav AnalysisType fieldType = bb.getMetaAccess().lookupJavaType(bb.getSnippetReflectionProvider().asObject(Object.class, fieldValue).getClass()); assert fieldType.isInstantiated() : fieldType; - /* - * *ALL* constants are scanned after each analysis iteration, thus the fieldType will - * eventually be added to the AllInstantiatedTypeFlow and the field type flow will - * eventually be updated. - */ - - if (bb.getAllInstantiatedTypeFlow().getState().containsType(fieldType)) { - /* Add the constant value object to the field's type flow. */ - FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver); - AnalysisObject constantObject = bb.analysisPolicy().createConstantObject(bb, fieldValue, fieldType); - if (!fieldTypeFlow.getState().containsObject(constantObject)) { - /* Add the new constant to the field's flow state. */ - TypeState constantTypeState = TypeState.forNonNullObject(bb, constantObject); - fieldTypeFlow.addState(bb, constantTypeState); - } + /* Add the constant value object to the field's type flow. */ + FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver); + AnalysisObject constantObject = bb.analysisPolicy().createConstantObject(bb, fieldValue, fieldType); + if (!fieldTypeFlow.getState().containsObject(constantObject)) { + /* Add the new constant to the field's flow state. */ + TypeState constantTypeState = TypeState.forNonNullObject(bb, constantObject); + fieldTypeFlow.addState(bb, constantTypeState); } } @@ -107,19 +99,13 @@ public void forNullArrayElement(JavaConstant array, AnalysisType arrayType, int @Override public void forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int elementIndex) { - /* - * *ALL* constants are scanned after each analysis iteration, thus the elementType will - * eventually be added to the AllInstantiatedTypeFlow and the array elements flow will - * eventually be updated. - */ - if (bb.getAllInstantiatedTypeFlow().getState().containsType(elementType)) { - ArrayElementsTypeFlow arrayObjElementsFlow = getArrayElementsFlow(array, arrayType); - AnalysisObject constantObject = bb.analysisPolicy().createConstantObject(bb, elementConstant, elementType); - if (!arrayObjElementsFlow.getState().containsObject(constantObject)) { - /* Add the constant element to the constant's array type flow. */ - TypeState elementTypeState = TypeState.forNonNullObject(bb, constantObject); - arrayObjElementsFlow.addState(bb, elementTypeState); - } + assert elementType.isInstantiated() : elementType; + ArrayElementsTypeFlow arrayObjElementsFlow = getArrayElementsFlow(array, arrayType); + AnalysisObject constantObject = bb.analysisPolicy().createConstantObject(bb, elementConstant, elementType); + if (!arrayObjElementsFlow.getState().containsObject(constantObject)) { + /* Add the constant element to the constant's array type flow. */ + TypeState elementTypeState = TypeState.forNonNullObject(bb, constantObject); + arrayObjElementsFlow.addState(bb, elementTypeState); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java index e781602f2aaa..c014b4de7fae 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java @@ -108,6 +108,7 @@ public abstract class BigBang { protected final boolean trackTypeFlowInputs; protected final boolean reportAnalysisStatistics; + protected final boolean extendedAsserts; /** * Processing queue. @@ -160,6 +161,7 @@ public BigBang(OptionValues options, AnalysisUniverse universe, HostedProviders if (reportAnalysisStatistics) { PointsToStats.init(this); } + extendedAsserts = PointstoOptions.ExtendedAsserts.getValue(options); unsafeLoads = new ConcurrentHashMap<>(); unsafeStores = new ConcurrentHashMap<>(); @@ -194,6 +196,10 @@ public boolean reportAnalysisStatistics() { return reportAnalysisStatistics; } + public boolean extendedAsserts() { + return extendedAsserts; + } + public OptionValues getOptions() { return options; } @@ -358,6 +364,10 @@ public TypeFlow getAllInstantiatedTypeFlow() { return objectType.getTypeFlow(this, true); } + public TypeState getAllInstantiatedTypes() { + return getAllInstantiatedTypeFlow().getState(); + } + public TypeFlow getAllSynchronizedTypeFlow() { return allSynchronizedTypeFlow; } @@ -369,7 +379,7 @@ public TypeState getAllSynchronizedTypeState() { * monitors. */ if (allSynchronizedTypeFlow.isSaturated()) { - return getAllInstantiatedTypeFlow().getState(); + return getAllInstantiatedTypes(); } return allSynchronizedTypeFlow.getState(); } @@ -627,7 +637,6 @@ private void checkObjectGraph() throws InterruptedException { } else { objectScanner.scanBootImageHeapRoots(null, null); } - AnalysisType.updateAssignableTypes(this); } public HeapScanningPolicy scanningPolicy() { @@ -821,7 +830,7 @@ public void printHeader() { @Override public void print() { - System.out.format("%5d %5d %5d |", numParsedGraphs.get(), getAllInstantiatedTypeFlow().getState().typesCount(), universe.getNextTypeId()); + System.out.format("%5d %5d %5d |", numParsedGraphs.get(), getAllInstantiatedTypes().typesCount(), universe.getNextTypeId()); super.print(); System.out.println(); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java index 30d8594024ec..8e17d38ae5e6 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java @@ -246,6 +246,7 @@ public final void scanConstant(JavaConstant value, ScanReason reason, WorklistEn return; } if (!bb.scanningPolicy().scanConstant(bb, value)) { + analysisType(bb, valueObj).registerAsInHeap(); return; } if (scannedObjects.putAndAcquire(valueObj) == null) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AccessFieldTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AccessFieldTypeFlow.java index a257bc13f9ba..4b8a8948692f 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AccessFieldTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AccessFieldTypeFlow.java @@ -66,7 +66,7 @@ public final boolean addState(BigBang bb, TypeState add) { */ protected TypeState filterObjectState(BigBang bb, TypeState objectState) { if (bb.analysisPolicy().relaxTypeFlowConstraints()) { - return TypeState.forIntersection(bb, objectState, field.getDeclaringClass().getTypeFlow(bb, true).getState()); + return TypeState.forIntersection(bb, objectState, field.getDeclaringClass().getAssignableTypes(true)); } return objectState; } 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 92ca5d765b06..87efd6738db3 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 @@ -30,8 +30,8 @@ public final class AllInstantiatedTypeFlow extends TypeFlow { - public AllInstantiatedTypeFlow(AnalysisType declaredType) { - super(declaredType, declaredType); + public AllInstantiatedTypeFlow(AnalysisType declaredType, boolean canBeNull) { + super(declaredType, declaredType, canBeNull); } public AllInstantiatedTypeFlow(AnalysisType declaredType, TypeState state) { 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 cc21181ad6b5..4da134027d8b 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 @@ -61,7 +61,7 @@ public boolean canSaturate() { @Override protected void onInputSaturated(BigBang bb, TypeFlow input) { /* - * When an array store is saturated conservativelly assume that the array can contain any + * When an array store is saturated conservatively assume that the array can contain any * subtype of its declared type. */ getDeclaredType().getTypeFlow(bb, true).addUse(bb, this); @@ -79,7 +79,7 @@ public TypeState filter(BigBang bb, TypeState update) { * conversion. At runtime that will throw an ArrayStoreException but during the analysis * we can detect such cases and filter out the incompatible types. */ - return TypeState.forIntersection(bb, update, declaredType.getTypeFlow(bb, true).getState()); + return TypeState.forIntersection(bb, update, declaredType.getAssignableTypes(true)); } } 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 26093777192e..6b20ce2fed5e 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 @@ -51,7 +51,7 @@ public TypeState filter(BigBang bb, TypeState update) { return update; } else { /* Filter the incoming state with the field type. */ - return TypeState.forIntersection(bb, update, declaredType.getTypeFlow(bb, true).getState()); + return TypeState.forIntersection(bb, update, declaredType.getAssignableTypes(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 e24a70df4cf9..fbd67c91130b 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 @@ -87,13 +87,13 @@ public boolean canSaturate() { @Override protected void onInputSaturated(BigBang bb, TypeFlow input) { /* - * When a field store is saturated conservativelly assume that the field state can contain + * When a field store is saturated conservatively assume that the field state can contain * any subtype of its declared type. */ getDeclaredType().getTypeFlow(bb, true).addUse(bb, this); } - /** The filter flow is used for unsafe writes and initialiazed on demand. */ + /** The filter flow is used for unsafe writes and initialized on demand. */ public FieldFilterTypeFlow filterFlow(BigBang bb) { assert source.isUnsafeAccessed() : "Filter flow requested for non unsafe accessed field."; 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 437195d15d64..01875e36a485 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 @@ -91,9 +91,9 @@ public TypeState filter(BigBang bb, TypeState update) { * instantiated sub-types). */ if (isAssignable) { - result = TypeState.forIntersection(bb, update, declaredType.getTypeFlow(bb, includeNull).getState()); + result = TypeState.forIntersection(bb, update, declaredType.getAssignableTypes(includeNull)); } else { - result = TypeState.forSubtraction(bb, update, declaredType.getTypeFlow(bb, !includeNull).getState()); + result = TypeState.forSubtraction(bb, update, declaredType.getAssignableTypes(!includeNull)); } } return result; @@ -111,7 +111,7 @@ protected void onInputSaturated(BigBang bb, TypeFlow input) { * swap-out will have no effect on those. However, if this flow is already marked as * saturated when the use or observer *lands*, even if that happens while/after * swapping-out, then the corresponding use or observer will be notified of its input - * saturation. Otherwise it may neighter get the saturation signal OR get swapped-out. + * saturation. Otherwise it may neither get the saturation signal OR get swapped-out. * * The downside in the later case is that the input/observer will lose the more precise * type information that swapping-out would have provided and will just use the more diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReceiverTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReceiverTypeFlow.java index 550554adfe50..852127123391 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReceiverTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReceiverTypeFlow.java @@ -58,20 +58,20 @@ public TypeState filter(BigBang bb, TypeState newState) { return declaredTypeFilter(bb, newState).forNonNull(bb); } + /** + * The formal receiver type flow, i.e., the type flow of the 'this' parameter, is linked with + * the actual receiver type flow through a non-state-transfer link, i.e., a link that exists + * only for a proper iteration of type flow graphs. This happens because the formal receiver , + * i.e., 'this' parameter, state must ONLY reflect those objects of the actual receiver that + * generated the context for the method clone which it belongs to. A direct link would instead + * transfer all the objects of compatible type from the actual receiver to the formal receiver. + * The formal receiver state for the non-initial parameters is updated through the + * FormalReceiverTypeFlow.addReceiverState method invoked directly from + * VirtualInvokeTypeFlow.update, SpecialInvokeTypeFlow.update or from + * InitialReceiverTypeFlow.update. + */ @Override public boolean addState(BigBang bb, TypeState add) { - /* - * The formal receiver type flow, i.e., the type flow of the 'this' parameter is linked with - * the actual receiver type flow through a non-state-transfer link, i.e., a link that exists - * only for a proper iteration of type flow graphs. This happens because the formal receiver - * , i.e., 'this' parameter, state must ONLY reflect those objects of the actual receiver - * that generated the context for the method clone which it belongs to. A direct link would - * instead transfer all the objects of compatible type from the actual receiver to the - * formal receiver. The formal receiver state for the non-initial parameters is updated - * through the FormalReceiverTypeFlow.addReceiverState method invoked directly from - * VirtualInvokeTypeFlow.update, SpecialInvokeTypeFlow.update or from - * InitialReceiverTypeFlow.update. - */ return false; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/InitialReceiverTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/InitialReceiverTypeFlow.java index 8a6e49bcf81d..c44178ed4937 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/InitialReceiverTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/InitialReceiverTypeFlow.java @@ -48,6 +48,20 @@ public TypeState filter(BigBang bb, TypeState newState) { return newState.forNonNull(bb); } + /** + * The state of the formal receiver type flow cannot be updated directly, thus + * {@link FormalReceiverTypeFlow#addReceiverState(BigBang, TypeState)} needs to be used. See + * {@link FormalReceiverTypeFlow#addState(BigBang, TypeState)} for a complete explanation. + */ + @Override + public boolean addUse(BigBang bb, TypeFlow use) { + boolean useAdded = super.addUse(bb, use); + if (useAdded) { + ((FormalReceiverTypeFlow) use).addReceiverState(bb, getState()); + } + return useAdded; + } + @Override public void update(BigBang bb) { TypeState curState = getState(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/InvokeTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/InvokeTypeFlow.java index e55164267389..c32e90ecd4eb 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/InvokeTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/InvokeTypeFlow.java @@ -149,7 +149,7 @@ public boolean addState(BigBang bb, TypeState add) { */ protected TypeState filterReceiverState(BigBang bb, TypeState invokeState) { if (bb.analysisPolicy().relaxTypeFlowConstraints()) { - return TypeState.forIntersection(bb, invokeState, receiverType.getTypeFlow(bb, true).getState()); + return TypeState.forIntersection(bb, invokeState, receiverType.getAssignableTypes(true)); } return invokeState; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java index 103cae1c5ad4..92e7be2e31d8 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java @@ -264,7 +264,7 @@ public TypeState filter(BigBang bb, TypeState update) { return update; } else { /* Filter the incoming state with the partition type. */ - return TypeState.forIntersection(bb, update, partitionType.getTypeFlow(bb, true).getState()); + return TypeState.forIntersection(bb, update, partitionType.getAssignableTypes(true)); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java index 4565b9d31f2f..11ddbba62cbc 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java @@ -358,7 +358,7 @@ public TypeState filter(BigBang bb, TypeState update) { return update; } else { /* Filter the incoming state with the partition type. */ - return TypeState.forIntersection(bb, update, partitionType.getTypeFlow(bb, true).getState()); + return TypeState.forIntersection(bb, update, partitionType.getAssignableTypes(true)); } } 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 074d0c8951e1..c0fbd9c3c251 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 @@ -140,6 +140,10 @@ public TypeFlow(T source, AnalysisType declaredType) { this(source, declaredType, TypeState.forEmpty(), -1, false, null); } + public TypeFlow(T source, AnalysisType declaredType, boolean canBeNull) { + this(source, declaredType, canBeNull ? TypeState.forNull() : TypeState.forEmpty(), -1, false, null); + } + public TypeFlow(T source, AnalysisType declaredType, TypeState state) { this(source, declaredType, state, -1, false, null); } @@ -237,7 +241,7 @@ public boolean isCloseToAllInstantiated(BigBang bb) { } public void setState(BigBang bb, TypeState state) { - assert !PointstoOptions.ExtendedAsserts.getValue(bb.getOptions()) || this instanceof InstanceOfTypeFlow || + assert !bb.extendedAsserts() || this instanceof InstanceOfTypeFlow || state.verifyDeclaredType(declaredType) : "declaredType: " + declaredType.toJavaName(true) + " state: " + state; this.state = state; } @@ -296,7 +300,7 @@ public boolean addState(BigBang bb, TypeState add, boolean postFlow) { PointsToStats.registerTypeFlowSuccessfulUpdate(bb, this, add); - assert !PointstoOptions.ExtendedAsserts.getValue(bb.getOptions()) || checkTypeState(bb, before, after); + assert !bb.extendedAsserts() || checkTypeState(bb, before, after); if (checkSaturated(bb, after)) { onSaturated(bb); @@ -308,7 +312,11 @@ public boolean addState(BigBang bb, TypeState add, boolean postFlow) { } private boolean checkTypeState(BigBang bb, TypeState before, TypeState after) { - assert PointstoOptions.ExtendedAsserts.getValue(bb.getOptions()); + assert bb.extendedAsserts(); + + if (bb.analysisPolicy().relaxTypeFlowConstraints()) { + return true; + } if (this instanceof InstanceOfTypeFlow || this instanceof FilterTypeFlow) { /* @@ -507,7 +515,7 @@ public TypeState filter(@SuppressWarnings("unused") BigBang bb, TypeState newSta /** * Filter type states using a flow's declared type. This is used when the type flow constraints * are relaxed to make sure that only compatible types are flowing through certain flows, e.g., - * stored to fields or passed to parameters. When the type flow constratints are not relaxed + * stored to fields or passed to parameters. When the type flow constraints are not relaxed * incompatible types flowing through such flows will result in an analysis error. */ public TypeState declaredTypeFilter(BigBang bb, TypeState newState) { @@ -523,8 +531,8 @@ public TypeState declaredTypeFilter(BigBang bb, TypeState newState) { /* If the declared type is Object type there is no need to filter. */ return newState; } - /* By default filter all type flows with the declared type. */ - return TypeState.forIntersection(bb, newState, declaredType.getTypeFlow(bb, true).getState()); + /* By default, filter all type flows with the declared type. */ + return TypeState.forIntersection(bb, newState, declaredType.getAssignableTypes(true)); } public void update(BigBang bb) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java index 43bd77601fd4..98a75adcfbf2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java @@ -28,19 +28,16 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; -import java.util.BitSet; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; import org.graalvm.compiler.debug.GraalError; import org.graalvm.compiler.graph.Node; @@ -53,7 +50,6 @@ import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.flow.AllInstantiatedTypeFlow; -import com.oracle.graal.pointsto.flow.TypeFlow; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.flow.context.object.ConstantContextSensitiveObject; import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; @@ -126,17 +122,6 @@ public class AnalysisType implements WrappedJavaType, OriginalClassProvider, Com */ private volatile ConstantContextSensitiveObject uniqueConstant; - /** - * Keeps track of the referenced types, i.e., concrete field types or array elements types - * discovered by the static analysis. - * - * This list is not update during the analysis and is filled lazily when requested through - * {@link #getReferencedTypes(BigBang)}. For complete results - * {@link #getReferencedTypes(BigBang)} should only be called when the base analysis has - * finished. - */ - private List referencedTypes; - /** * Cache for the resolved methods. * @@ -228,7 +213,6 @@ public enum UsageKind { this.id = universe.nextTypeId.getAndIncrement(); /* Set the context insensitive analysis object so that it has access to its type id. */ this.contextInsensitiveAnalysisObject = new AnalysisObject(universe, this); - this.referencedTypes = null; assert getSuperclass() == null || getId() > getSuperclass().getId(); @@ -256,8 +240,10 @@ public AnalysisType getArrayClass(int dim) { } public void cleanupAfterAnalysis() { - assignableTypes = null; - assignableTypesNonNull = null; + instantiatedTypes = null; + instantiatedTypesNonNull = null; + assignableTypesState = null; + assignableTypesNonNullState = null; contextInsensitiveAnalysisObject = null; constantObjectsCache = null; uniqueConstant = null; @@ -331,83 +317,52 @@ private void mergeConstantObjects(BigBang bb) { } /** - * Returns the list of referenced types, i.e., concrete field types or array elements types - * discovered by the static analysis. - * - * Since this list is not updated during the analysis, for complete results this should only be - * called when the base analysis has finished. + * Stores the list of all assignable types for each analysis type. The assignable list is + * updated whenever a new subtype is created. This is used to filter the types assignable to a + * specific type from an input type state. */ - public List getReferencedTypes(BigBang bb) { - - if (referencedTypes == null) { - - Set referencedTypesSet = new HashSet<>(); - - if (this.isArray()) { - if (this.getContextInsensitiveAnalysisObject().isObjectArray()) { - /* Collect the types referenced through index store (for arrays). */ - for (AnalysisType type : getContextInsensitiveAnalysisObject().getArrayElementsFlow(bb, false).getState().types()) { - /* Add the assignable types, as discovered by the static analysis. */ - type.getTypeFlow(bb, false).getState().types().forEach(referencedTypesSet::add); - } - } - } else { - /* Collect the field referenced types. */ - for (AnalysisField field : getInstanceFields(true)) { - TypeState state = field.getInstanceFieldTypeState(); - for (AnalysisType type : state.types()) { - /* Add the assignable types, as discovered by the static analysis. */ - type.getTypeFlow(bb, false).getState().types().forEach(referencedTypesSet::add); - } - } - } - - referencedTypes = new ArrayList<>(referencedTypesSet); - } + public TypeState assignableTypesState = TypeState.forNull(); + public TypeState assignableTypesNonNullState = TypeState.forEmpty(); - return referencedTypes; - } - - public volatile AllInstantiatedTypeFlow assignableTypes; - public volatile AllInstantiatedTypeFlow assignableTypesNonNull; - - public AllInstantiatedTypeFlow getTypeFlow(BigBang bb, boolean includeNull) { - if (assignableTypes == null) { - createTypeFlows(bb); - } + /** + * Type flows containing all the instantiated sub-types. This is a sub set of the all assignable + * types. These flows are 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. + */ + public AllInstantiatedTypeFlow instantiatedTypes = new AllInstantiatedTypeFlow(this, true); + public AllInstantiatedTypeFlow instantiatedTypesNonNull = new AllInstantiatedTypeFlow(this, false); + /* + * Returns a type flow containing all types that are assignable from this type and are also + * instantiated. + */ + public AllInstantiatedTypeFlow getTypeFlow(@SuppressWarnings("unused") BigBang bb, boolean includeNull) { if (includeNull) { - return assignableTypes; + return instantiatedTypes; } else { - return assignableTypesNonNull; + return instantiatedTypesNonNull; } } - private synchronized void createTypeFlows(BigBang bb) { - if (assignableTypes != null) { - return; + /** + * Returns the assignable types. Assignable types are updated on analysis type creation. The + * types in this list are not guaranteed to be instantiated and should only be used for filter + * operations. + */ + public TypeState getAssignableTypes(boolean includeNull) { + if (includeNull) { + return assignableTypesState; + } else { + return assignableTypesNonNullState; } - - /* - * Do not publish the new flows here, before they have been completely initialized. Other - * threads must not pick up partially initialized type flows. - */ - AllInstantiatedTypeFlow newAssignableTypes = new AllInstantiatedTypeFlow(this); - AllInstantiatedTypeFlow newAssignableTypesNonNull = new AllInstantiatedTypeFlow(this); - - updateTypeFlows(bb, newAssignableTypes, newAssignableTypesNonNull); - - /* We perform the null-check on assignableTypes, so publish that one last. */ - assignableTypesNonNull = newAssignableTypesNonNull; - assignableTypes = newAssignableTypes; - } public static boolean verifyAssignableTypes(BigBang bb) { List allTypes = bb.getUniverse().getTypes(); Set mismatchedAssignableResults = ConcurrentHashMap.newKeySet(); - allTypes.parallelStream().filter(t -> t.assignableTypes != null).forEach(t1 -> { + allTypes.parallelStream().filter(t -> t.instantiatedTypes != null).forEach(t1 -> { for (AnalysisType t2 : allTypes) { boolean expected; if (t2.isInstantiated()) { @@ -415,7 +370,7 @@ public static boolean verifyAssignableTypes(BigBang bb) { } else { expected = false; } - boolean actual = t1.assignableTypes.getState().containsType(t2); + boolean actual = t1.instantiatedTypes.getState().containsType(t2); if (actual != expected) { mismatchedAssignableResults.add("assignableTypes mismatch: " + @@ -432,129 +387,10 @@ public static boolean verifyAssignableTypes(BigBang bb) { return true; } - public static void updateAssignableTypes(BigBang bb) { - /* - * Update the assignable-state for all types. So do not post any update operations before - * the computation is finished, because update operations must not see any intermediate - * state. - */ - List allTypes = bb.getUniverse().getTypes(); - List> changedFlows = new ArrayList<>(); - - Map newAssignableTypes = new HashMap<>(); - for (AnalysisType type : allTypes) { - if (type.isInstantiated()) { - int arrayDimension = type.dimension; - AnalysisType elementalType = type.elementalType; - - addTypeToAssignableLists(type.getId(), elementalType, arrayDimension, newAssignableTypes, true); - for (int i = 0; i < arrayDimension; i++) { - addTypeToAssignableLists(type.getId(), type, i, newAssignableTypes, false); - } - if (arrayDimension > 0 && !elementalType.isPrimitive()) { - addTypeToAssignableLists(type.getId(), bb.getObjectType(), arrayDimension, newAssignableTypes, true); - } - } - } - for (AnalysisType type : allTypes) { - if (type.assignableTypes == null) { - /* - * Computing assignable types in bulk here is much cheaper than doing it - * individually when needed in updateTypeFlows. - */ - type.assignableTypes = new AllInstantiatedTypeFlow(type, TypeState.forNull()); - type.assignableTypesNonNull = new AllInstantiatedTypeFlow(type, TypeState.forEmpty()); - } - TypeState assignableTypeState = TypeState.forNull(); - if (newAssignableTypes.get(type.getId()) != null) { - BitSet assignableTypes = newAssignableTypes.get(type.getId()); - if (type.assignableTypes.getState().hasExactTypes(assignableTypes)) { - /* Avoid creation of the expensive type state. */ - continue; - } - assignableTypeState = TypeState.forExactTypes(bb, newAssignableTypes.get(type.getId()), true); - } - - updateFlow(bb, type.assignableTypes, assignableTypeState, changedFlows); - updateFlow(bb, type.assignableTypesNonNull, assignableTypeState.forNonNull(bb), changedFlows); - } - - for (TypeFlow changedFlow : changedFlows) { - bb.postFlow(changedFlow); - } - } - - private static void addTypeToAssignableLists(int typeIdToAdd, AnalysisType elementalType, int arrayDimension, Map newAssignableTypes, boolean processType) { - if (elementalType == null) { - return; - } - if (processType) { - int addToId = elementalType.getArrayClass(arrayDimension).getId(); - BitSet addToBitSet = newAssignableTypes.computeIfAbsent(addToId, BitSet::new); - addToBitSet.set(typeIdToAdd); - } - addTypeToAssignableLists(typeIdToAdd, elementalType.getSuperclass(), arrayDimension, newAssignableTypes, true); - for (AnalysisType interf : elementalType.getInterfaces()) { - addTypeToAssignableLists(typeIdToAdd, interf, arrayDimension, newAssignableTypes, true); - } - } - - /** Called when the list of assignable types of a type is first initialized. */ - private void updateTypeFlows(BigBang bb, TypeFlow assignable, TypeFlow assignableNonNull) { - if (isPrimitive() || isJavaLangObject()) { - return; - } - - AnalysisType superType; - if (isInterface()) { - /* - * For interfaces, we have to search all instantiated types, i.e., start at - * java.lang.Object - */ - superType = bb.getObjectType(); - } else { - /* - * Find the closest supertype that has assignable-information computed. That is the best - * starting point. - */ - superType = getSuperclass(); - while (superType.assignableTypes == null) { - superType = superType.getSuperclass(); - } - } - - TypeState superAssignableTypeState = superType.assignableTypes.getState(); - BitSet assignableTypesSet = new BitSet(); - for (AnalysisType type : superAssignableTypeState.types()) { - if (this.isAssignableFrom(type)) { - assignableTypesSet.set(type.getId()); - } - } - - TypeState assignableTypeState = TypeState.forExactTypes(bb, assignableTypesSet, true); - - updateFlow(bb, assignable, assignableTypeState); - updateFlow(bb, assignableNonNull, assignableTypeState.forNonNull(bb)); - } - - private static void updateFlow(BigBang bb, TypeFlow flow, TypeState newState) { - updateFlow(bb, flow, newState, null); - } - - private static void updateFlow(BigBang bb, TypeFlow flow, TypeState newState, List> changedFlows) { - if (!flow.getState().equals(newState)) { - flow.setState(bb, newState); - if (changedFlows != null && (flow.getUses().size() > 0 || flow.getObservers().size() > 0)) { - changedFlows.add(flow); - } - } - } - public boolean registerAsInHeap() { registerAsReachable(); if (AtomicUtils.atomicMark(isInHeap)) { - assert isArray() || (isInstanceClass() && !Modifier.isAbstract(getModifiers())) : this; - universe.hostVM.checkForbidden(this, UsageKind.InHeap); + registerAsInstantiated(UsageKind.InHeap); return true; } return false; @@ -566,65 +402,118 @@ public boolean registerAsInHeap() { public boolean registerAsAllocated(Node node) { registerAsReachable(); if (AtomicUtils.atomicMark(isAllocated)) { - assert isArray() || (isInstanceClass() && !Modifier.isAbstract(getModifiers())) : this; - universe.hostVM.checkForbidden(this, UsageKind.Allocated); + registerAsInstantiated(UsageKind.Allocated); return true; } return false; } + /** Register the type as instantiated with all its super types. */ + private void registerAsInstantiated(UsageKind usageKind) { + assert isAllocated.get() || isInHeap.get(); + assert isArray() || (isInstanceClass() && !Modifier.isAbstract(getModifiers())) : this; + universe.hostVM.checkForbidden(this, usageKind); + + BigBang bb = universe.getBigbang(); + TypeState typeState = TypeState.forExactType(bb, this, true); + TypeState typeStateNonNull = TypeState.forExactType(bb, this, false); + + /* Register the instantiated type with its super types. */ + forAllSuperTypes(t -> { + t.instantiatedTypes.addState(bb, typeState); + t.instantiatedTypesNonNull.addState(bb, typeStateNonNull); + }); + } + + /** + * Register the type as assignable with all its super types. This is a blocking call to ensure + * that the type is registered with all its super types before it is propagated by the analysis + * through type flows. + */ + public void registerAsAssignable(BigBang bb) { + TypeState typeState = TypeState.forType(bb, this, true); + /* + * Register the assignable type with its super types. Skip this type, it can lead to a + * deadlock when this is called when the type is created. + */ + forAllSuperTypes(t -> t.addAssignableType(bb, typeState), false); + /* Register the type as assignable to itself. */ + this.addAssignableType(bb, typeState); + } + public boolean registerAsReachable() { if (!isReachable.get()) { - if (superClass != null) { + forAllSuperTypes(AnalysisType::markReachable); + return true; + } + return false; + } + + private void markReachable() { + if (AtomicUtils.atomicMark(isReachable)) { + universe.hostVM.checkForbidden(this, UsageKind.Reachable); + if (isArray()) { /* - * The super class must be registered as reachable before this class because other - * threads may query the fields which also collects the super class fields. Field - * lookup guarantees that the type has already been marked as reachable. + * For array types, distinguishing between "used" and "instantiated" does not + * provide any benefits since array types do not implement new methods. Marking all + * used array types as instantiated too allows more usages of Arrays.newInstance + * without the need of explicit registration of types for reflection. */ - superClass.registerAsReachable(); - } - for (AnalysisType iface : interfaces) { - iface.registerAsReachable(); + registerAsAllocated(null); } - if (AtomicUtils.atomicMark(isReachable)) { - universe.hostVM.checkForbidden(this, UsageKind.Reachable); - if (isArray()) { - /* - * For array types, distinguishing between "used" and "instantiated" does not - * provide any benefits since array types do not implement new methods. Marking - * all used array types as instantiated too allows more usages of - * Arrays.newInstance without the need of explicit registration of types for - * reflection. - */ - registerAsAllocated(null); - - componentType.registerAsReachable(); - - /* - * For a class B extends A, the array type A[] is not a superclass of the array - * type B[]. So there is no strict need to make A[] reachable when B[] is - * reachable. But it turns out that this is puzzling for users, and there are - * frameworks that instantiate such arrays programmatically using - * Array.newInstance(). To reduce the amount of manual configuration that is - * necessary, we mark all array types of the elemental supertypes and - * superinterfaces also as reachable. - */ - for (int i = 1; i <= dimension; i++) { - if (elementalType.superClass != null) { - elementalType.superClass.getArrayClass(i).registerAsReachable(); - } - for (AnalysisType iface : elementalType.interfaces) { - iface.getArrayClass(i).registerAsReachable(); - } - } - } + universe.hostVM.executor().execute(initializationTask); + } + } - /* Schedule the registration task. */ - universe.hostVM.executor().execute(initializationTask); - return true; - } + /** + * Iterates all super types for this type, where a super type is defined as any type that is + * assignable from this type, feeding each of them to the consumer. + * + * For a class B extends A, the array type A[] is not a superclass of the array type B[]. So + * there is no strict need to make A[] reachable when B[] is reachable. But it turns out that + * this is puzzling for users, and there are frameworks that instantiate such arrays + * programmatically using Array.newInstance(). To reduce the amount of manual configuration that + * is necessary, we mark all array types of the elemental supertypes and superinterfaces also as + * reachable. + * + * Moreover, even if B extends A doesn't imply that B[] extends A[] it does imply that + * A[].isAssignableFrom(B[]). + * + * NOTE: This method doesn't guarantee that a super type will only be processed once. For + * example when java.lang.Class is processed its interface java.lang.reflect.AnnotatedElement is + * reachable directly, but also through java.lang.GenericDeclaration, so it will be processed + * twice. + */ + private void forAllSuperTypes(Consumer superTypeConsumer) { + forAllSuperTypes(superTypeConsumer, true); + } + + private void forAllSuperTypes(Consumer superTypeConsumer, boolean includeThisType) { + forAllSuperTypes(elementalType, dimension, includeThisType, superTypeConsumer); + for (int i = 0; i < dimension; i++) { + forAllSuperTypes(this, i, false, superTypeConsumer); + } + if (dimension > 0 && !elementalType.isPrimitive() && !elementalType.isJavaLangObject()) { + forAllSuperTypes(universe.objectType(), dimension, true, superTypeConsumer); } - return false; + } + + private static void forAllSuperTypes(AnalysisType elementType, int arrayDimension, boolean processType, Consumer superTypeConsumer) { + if (elementType == null) { + return; + } + if (processType) { + superTypeConsumer.accept(elementType.getArrayClass(arrayDimension)); + } + for (AnalysisType interf : elementType.getInterfaces()) { + forAllSuperTypes(interf, arrayDimension, true, superTypeConsumer); + } + forAllSuperTypes(elementType.getSuperclass(), arrayDimension, true, superTypeConsumer); + } + + private synchronized void addAssignableType(BigBang bb, TypeState typeState) { + assignableTypesState = TypeState.forUnion(bb, assignableTypesState, typeState); + assignableTypesNonNullState = assignableTypesState.forNonNull(bb); } public void ensureInitialized() { @@ -1059,6 +948,7 @@ private AnalysisField[] convertFields(ResolvedJavaField[] original, List(), false); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java index cb11da36dc58..ae3a19505625 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java @@ -114,6 +114,7 @@ public class AnalysisUniverse implements Universe { private AnalysisType objectClass; private final JavaKind wordKind; private AnalysisPolicy analysisPolicy; + private BigBang bigbang; public JavaKind getWordKind() { return wordKind; @@ -291,6 +292,11 @@ private AnalysisType createType(ResolvedJavaType type) { */ hostVM.registerType(newValue); + /* Register the type as assignable with all its super types before it is published. */ + if (bigbang != null) { + newValue.registerAsAssignable(bigbang); + } + /* * Now that our type is correctly registered in the id-to-type array, make it accessible * by other threads. @@ -361,6 +367,7 @@ public JavaField lookupAllowUnresolved(JavaField rawField) { * it during constant folding. */ AnalysisType declaringType = lookup(field.getDeclaringClass()); + declaringType.registerAsReachable(); declaringType.ensureInitialized(); } @@ -695,4 +702,12 @@ public AnalysisPolicy analysisPolicy() { public MetaAccessProvider getOriginalMetaAccess() { return originalMetaAccess; } + + public void setBigBang(BigBang bigbang) { + this.bigbang = bigbang; + } + + public BigBang getBigbang() { + return bigbang; + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java index 449020f164e3..145bdafde211 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/MultiTypeState.java @@ -28,10 +28,7 @@ import java.util.BitSet; import java.util.Iterator; -import org.graalvm.compiler.options.OptionValues; - import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; @@ -82,7 +79,7 @@ public class MultiTypeState extends TypeState { this.merged = false; assert typesCount > 1 : "Multi type state with single type."; assert objects.length > 1 : "Multi type state with single object."; - assert !PointstoOptions.ExtendedAsserts.getValue(bb.getOptions()) || checkObjects(bb.getOptions()); + assert !bb.extendedAsserts() || checkObjects(bb); PointsToStats.registerTypeState(bb, this); } @@ -116,8 +113,8 @@ public int[] getObjectTypeIds() { return objectTypeIds; } - private boolean checkObjects(OptionValues options) { - assert PointstoOptions.ExtendedAsserts.getValue(options); + private boolean checkObjects(BigBang bb) { + assert bb.extendedAsserts(); for (int idx = 0; idx < objects.length - 1; idx++) { AnalysisObject o0 = objects[idx]; @@ -333,7 +330,7 @@ public void noteMerge(BigBang bb) { @Override public boolean closeToAllInstantiated(BigBang bb) { if (typesCount > 200 && bb != null) { - MultiTypeState allInstState = (MultiTypeState) bb.getAllInstantiatedTypeFlow().getState(); + MultiTypeState allInstState = (MultiTypeState) bb.getAllInstantiatedTypes(); return typesCount * 100L / allInstState.typesCount > 75; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java index 156e0ce581cf..899f6c117f63 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/typestate/SingleTypeState.java @@ -29,10 +29,7 @@ import java.util.BitSet; import java.util.Iterator; -import org.graalvm.compiler.options.OptionValues; - import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.meta.AnalysisType; @@ -58,7 +55,7 @@ public SingleTypeState(BigBang bb, boolean canBeNull, int properties, AnalysisOb this.canBeNull = canBeNull; this.merged = false; assert objects.length > 0 : "Single type state with no objects."; - assert !PointstoOptions.ExtendedAsserts.getValue(bb.getOptions()) || checkObjects(bb.getOptions()); + assert !bb.extendedAsserts() || checkObjects(bb); PointsToStats.registerTypeState(bb, this); } @@ -74,8 +71,8 @@ protected SingleTypeState(BigBang bb, boolean canBeNull, SingleTypeState other) PointsToStats.registerTypeState(bb, this); } - protected boolean checkObjects(OptionValues options) { - assert PointstoOptions.ExtendedAsserts.getValue(options); + protected boolean checkObjects(BigBang bb) { + assert bb.extendedAsserts(); /* Check that the objects array are sorted by type. */ for (int idx = 0; idx < objects.length - 1; idx++) { 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 ff691e5b1e5c..4b5798c29ce5 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 @@ -333,6 +333,14 @@ public static TypeState forExactType(BigBang bb, AnalysisObject object, boolean return new SingleTypeState(bb, canBeNull, bb.analysisPolicy().makePoperties(bb, object), object); } + public static TypeState forType(BigBang bb, AnalysisType type, boolean canBeNull) { + return forType(bb, type.getContextInsensitiveAnalysisObject(), canBeNull); + } + + public static TypeState forType(BigBang bb, AnalysisObject object, boolean canBeNull) { + return new SingleTypeState(bb, canBeNull, bb.analysisPolicy().makePoperties(bb, object), object); + } + public static TypeState forExactTypes(BigBang bb, BitSet exactTypes, boolean canBeNull) { int numTypes = exactTypes.cardinality(); if (numTypes == 0) { @@ -424,6 +432,11 @@ public static TypeState forUnion(BigBang bb, TypeState s1, TypeState s2) { } public static TypeState forIntersection(BigBang bb, TypeState s1, TypeState s2) { + /* + * All filtered types (s1) must be marked as instantiated to ensures that the filter state + * (s2) has been updated before a type appears in the input, otherwise types can be missed. + */ + assert !bb.extendedAsserts() || checkTypes(s1); if (s1.isEmpty()) { return s1; } else if (s1.isNull()) { @@ -445,6 +458,11 @@ public static TypeState forIntersection(BigBang bb, TypeState s1, TypeState s2) } public static TypeState forSubtraction(BigBang bb, TypeState s1, TypeState s2) { + /* + * All filtered types (s1) must be marked as instantiated to ensures that the filter state + * (s2) has been updated before a type appears in the input, otherwise types can be missed. + */ + assert !bb.extendedAsserts() || checkTypes(s1); if (s1.isEmpty()) { return s1; } else if (s1.isNull()) { @@ -465,6 +483,16 @@ public static TypeState forSubtraction(BigBang bb, TypeState s1, TypeState s2) { } } + private static boolean checkTypes(TypeState state) { + for (AnalysisType type : state.types()) { + if (!type.isInstantiated()) { + System.out.println("Processing a type not yet marked as instantiated: " + type.getName()); + return false; + } + } + return true; + } + /* Implementation of union. */ private static TypeState doUnion(BigBang bb, SingleTypeState s1, SingleTypeState s2) { @@ -488,7 +516,7 @@ private static TypeState doUnion(BigBang bb, SingleTypeState s1, SingleTypeState } /* Due to the test above the union set cannot be equal to any of the two arrays. */ - assert !PointstoOptions.ExtendedAsserts.getValue(bb.getOptions()) || !Arrays.equals(resultObjects, s1.objects) && !Arrays.equals(resultObjects, s2.objects); + assert !bb.extendedAsserts() || !Arrays.equals(resultObjects, s1.objects) && !Arrays.equals(resultObjects, s2.objects); /* Create the resulting exact type state. */ SingleTypeState result = new SingleTypeState(bb, resultCanBeNull, bb.analysisPolicy().makePopertiesForUnion(s1, s2), resultObjects); @@ -552,7 +580,7 @@ private static TypeState doUnion(BigBang bb, MultiTypeState s1, SingleTypeState * Due to the test above and to the fact that TypeStateUtils.union checks if one array * contains the other the union set cannot be equal to s1's objects slice. */ - assert !PointstoOptions.ExtendedAsserts.getValue(bb.getOptions()) || !Arrays.equals(unionObjects, s1ObjectsSlice); + assert !bb.extendedAsserts() || !Arrays.equals(unionObjects, s1ObjectsSlice); /* * Replace the s1 objects slice of the same type as s2 with the union objects and create @@ -886,7 +914,7 @@ private static TypeState doIntersection(BigBang bb, SingleTypeState s1, SingleTy } private static TypeState doIntersection(BigBang bb, SingleTypeState s1, MultiTypeState s2) { - assert !PointstoOptions.ExtendedAsserts.getValue(bb.getOptions()) || TypeStateUtils.isContextInsensitiveTypeState(s2) : "Current implementation limitation."; + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(s2) : "Current implementation limitation."; boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); @@ -917,7 +945,7 @@ private static TypeState doIntersection(BigBang bb, MultiTypeState s1, SingleTyp } private static TypeState doIntersection(BigBang bb, MultiTypeState s1, MultiTypeState s2) { - assert !PointstoOptions.ExtendedAsserts.getValue(bb.getOptions()) || TypeStateUtils.isContextInsensitiveTypeState(s2) : "Current implementation limitation."; + assert !bb.extendedAsserts() || TypeStateUtils.isContextInsensitiveTypeState(s2) : "Current implementation limitation."; boolean resultCanBeNull = s1.canBeNull() && s2.canBeNull(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ThreadGroup.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ThreadGroup.java index 144132a9a14b..45a1560f6fb8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ThreadGroup.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ThreadGroup.java @@ -24,13 +24,13 @@ */ package com.oracle.svm.core.thread; -import com.oracle.svm.core.annotate.TargetElement; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.jdk.NotLoomJDK; import jdk.vm.ci.meta.MetaAccessProvider; @@ -40,7 +40,7 @@ final class Target_java_lang_ThreadGroup { @Alias @TargetElement(onlyWith = NotLoomJDK.class)// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupNUnstartedThreadsRecomputation.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupNUnstartedThreadsRecomputation.class, disableCaching = true)// private int nUnstartedThreads; @Alias @TargetElement(onlyWith = NotLoomJDK.class)// @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupNThreadsRecomputation.class)// @@ -49,9 +49,16 @@ final class Target_java_lang_ThreadGroup { @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupThreadsRecomputation.class)// private Thread[] threads; - @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupNGroupsRecomputation.class)// + /* + * JavaThreadsFeature.reachableThreadGroups is updated in an object replacer, during analysis, + * thus the recomputation may see an incomplete value. By disabling caching eventually the + * recomputed value will be the correct one. No additional caching is necessary since + * reachableThreadGroups will eventually reach a stable value during analysis and there is no + * risk to discover new objects in later phases. + */ + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupNGroupsRecomputation.class, disableCaching = true)// private int ngroups; - @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupGroupsRecomputation.class)// + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupGroupsRecomputation.class, disableCaching = true)// private ThreadGroup[] groups; @Alias 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 54f0b5766ddd..9868266929e8 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 @@ -24,8 +24,8 @@ */ package com.oracle.svm.hosted; -import static com.oracle.svm.hosted.NativeImageOptions.DiagnosticsMode; import static com.oracle.svm.hosted.NativeImageOptions.DiagnosticsDir; +import static com.oracle.svm.hosted.NativeImageOptions.DiagnosticsMode; import static org.graalvm.compiler.hotspot.JVMCIVersionCheck.JVMCI11_RELEASES_URL; import static org.graalvm.compiler.hotspot.JVMCIVersionCheck.JVMCI8_RELEASES_URL; import static org.graalvm.compiler.replacements.StandardGraphBuilderPlugins.registerInvocationPlugins; @@ -881,6 +881,13 @@ private void setupNativeImage(String imageName, OptionValues options, Map t.registerAsAssignable(bigbang)); + boolean withoutCompilerInvoker = CAnnotationProcessorCache.Options.ExitAfterQueryCodeGeneration.getValue() || (NativeImageOptions.ExitAfterRelocatableImageWrite.getValue() && CAnnotationProcessorCache.Options.UseCAPCache.getValue()); @@ -892,10 +899,6 @@ private void setupNativeImage(String imageName, OptionValues options, Map feature.duringSetup(config)); @@ -1038,13 +1041,11 @@ public static void initializeBigBang(Inflation bigbang, OptionValues options, Fe } } - public static Inflation createBigBang(OptionValues options, TargetDescription target, AnalysisUniverse aUniverse, NativeLibraries nativeLibraries, ForkJoinPool analysisExecutor, - Runnable heartbeatCallback, - AnalysisMetaAccess aMetaAccess, AnalysisConstantReflectionProvider aConstantReflection, WordTypes aWordTypes, SnippetReflectionProvider aSnippetReflection, - AnnotationSubstitutionProcessor annotationSubstitutionProcessor, ForeignCallsProvider aForeignCalls, ClassInitializationSupport classInitializationSupport, - Providers originalProviders) { + public static Inflation createBigBang(OptionValues options, TargetDescription target, AnalysisUniverse aUniverse, ForkJoinPool analysisExecutor, + Runnable heartbeatCallback, AnalysisMetaAccess aMetaAccess, AnalysisConstantReflectionProvider aConstantReflection, WordTypes aWordTypes, + SnippetReflectionProvider aSnippetReflection, AnnotationSubstitutionProcessor annotationSubstitutionProcessor, ForeignCallsProvider aForeignCalls, + ClassInitializationSupport classInitializationSupport, Providers originalProviders) { assert aUniverse != null : "Analysis universe must be initialized."; - assert nativeLibraries != null : "Native libraries must be set."; aMetaAccess.lookupJavaType(String.class).registerAsReachable(); AnalysisConstantFieldProvider aConstantFieldProvider = new AnalysisConstantFieldProvider(aUniverse, aMetaAccess, aConstantReflection, classInitializationSupport); /* @@ -1453,7 +1454,7 @@ private void checkUniverse() { if (parameterState != null) { AnalysisType declaredType = method.getTypeFlow().getOriginalMethodFlows().getParameter(i).getDeclaredType(); if (declaredType.isInterface()) { - TypeState declaredTypeState = declaredType.getTypeFlow(bigbang, true).getState(); + TypeState declaredTypeState = declaredType.getAssignableTypes(true); parameterState = TypeState.forSubtraction(bigbang, parameterState, declaredTypeState); if (!parameterState.isEmpty()) { String methodKey = method.format("%H.%n(%p)"); @@ -1470,7 +1471,7 @@ private void checkUniverse() { if (state != null) { AnalysisType declaredType = field.getType(); if (declaredType.isInterface()) { - state = TypeState.forSubtraction(bigbang, state, declaredType.getTypeFlow(bigbang, true).getState()); + state = TypeState.forSubtraction(bigbang, state, declaredType.getAssignableTypes(true)); if (!state.isEmpty()) { String fieldKey = field.format("%H.%n"); bigbang.getUnsupportedFeatures().addMessage(fieldKey, null, diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java index 0c3d255014a3..769fdf8f687f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java @@ -849,6 +849,8 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv disableCaching = recomputeAnnotation.disableCaching(); guarantee(!isFinal || ComputedValueField.isFinalValid(kind), "@%s with %s can never be final during analysis: unset isFinal in the annotation on %s", RecomputeFieldValue.class.getSimpleName(), kind, annotated); + guarantee(!isFinal || !disableCaching, "@%s can not be final if caching is disabled: unset isFinal in the annotation on %s", + RecomputeFieldValue.class.getSimpleName(), kind, annotated); if (recomputeAnnotation.declClass() != RecomputeFieldValue.class) { guarantee(recomputeAnnotation.declClassName().isEmpty(), "Both class and class name specified"); targetClass = recomputeAnnotation.declClass(); From 58bb7b7469fb134c4b7e55d71ef1165610ae28b2 Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Fri, 23 Jul 2021 14:30:50 -0700 Subject: [PATCH 2/2] Make filter swap-out deterministic. --- .../graal/pointsto/DefaultAnalysisPolicy.java | 2 +- .../pointsto/flow/FieldFilterTypeFlow.java | 10 ++++++ .../graal/pointsto/flow/FilterTypeFlow.java | 36 ++++++++++--------- .../pointsto/flow/FormalParamTypeFlow.java | 2 +- .../oracle/graal/pointsto/flow/TypeFlow.java | 36 +++++++++++++------ 5 files changed, 58 insertions(+), 28 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java index 0a92e92b3e01..814400b20599 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java @@ -248,7 +248,7 @@ public void onObservedSaturated(BigBang bb, TypeFlow observed) { /* * The receiver object flow of the invoke operation is saturated; it will stop sending - * notificatons. Swap the invoke flow with the unique, context-insensitive invoke flow + * notifications. Swap the invoke flow with the unique, context-insensitive invoke flow * corresponding to the target method, which is already registered as an observer for * the type flow of the receiver type and therefore saturated. This is a conservative * approximation and this invoke will reach all possible callees. 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 6b20ce2fed5e..44f7656b1806 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 @@ -62,6 +62,16 @@ protected void onInputSaturated(BigBang bb, TypeFlow input) { swapOut(bb, declaredType.getTypeFlow(bb, true)); } + @Override + protected void notifyUseOfSaturation(BigBang bb, TypeFlow use) { + swapAtUse(bb, declaredType.getTypeFlow(bb, true), use); + } + + @Override + protected void notifyObserverOfSaturation(BigBang bb, TypeFlow observer) { + swapAtObserver(bb, declaredType.getTypeFlow(bb, true), observer); + } + @Override public String toString() { return "FieldFilterTypeFlow<" + source + ">"; 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 01875e36a485..8ac7a9a7feab 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 @@ -102,28 +102,32 @@ public TypeState filter(BigBang bb, TypeState update) { @Override protected void onInputSaturated(BigBang bb, TypeFlow input) { if (isAssignable) { - TypeFlow sourceFlow = declaredType.getTypeFlow(bb, includeNull); - - /* - * First mark this flow as saturated, then swap it out at its uses/observers with its - * declared type flow. Marking this flow as saturated first is important: if there are - * any uses or observers *in-flight*, i.e., not yet registered at this point, trying to - * swap-out will have no effect on those. However, if this flow is already marked as - * saturated when the use or observer *lands*, even if that happens while/after - * swapping-out, then the corresponding use or observer will be notified of its input - * saturation. Otherwise it may neither get the saturation signal OR get swapped-out. - * - * The downside in the later case is that the input/observer will lose the more precise - * type information that swapping-out would have provided and will just use the more - * conservative approximation, e.g., the target method declared type for invokes. - */ + /* Swap this flow out at its uses/observers with its declared type flow. */ setSaturated(); - swapOut(bb, sourceFlow); + swapOut(bb, declaredType.getTypeFlow(bb, includeNull)); } else { super.onInputSaturated(bb, input); } } + @Override + protected void notifyUseOfSaturation(BigBang bb, TypeFlow use) { + if (isAssignable) { + swapAtUse(bb, declaredType.getTypeFlow(bb, includeNull), use); + } else { + super.notifyUseOfSaturation(bb, use); + } + } + + @Override + protected void notifyObserverOfSaturation(BigBang bb, TypeFlow observer) { + if (isAssignable) { + swapAtObserver(bb, declaredType.getTypeFlow(bb, includeNull), observer); + } else { + super.notifyObserverOfSaturation(bb, observer); + } + } + @Override public boolean addState(BigBang bb, TypeState add) { assert this.isClone(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalParamTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalParamTypeFlow.java index dc2c1868b694..ec1762946b0e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalParamTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalParamTypeFlow.java @@ -78,7 +78,7 @@ public int position() { @Override public String toString() { StringBuilder str = new StringBuilder(); - str.append("FormalParamFlow").append("[").append(method.format("%H.%n")).append("]").append("[").append(position).append("]<").append(getState()).append(">"); + str.append("FormalParamFlow").append("[").append(method.format("%H.%n(%p)")).append("]").append("[").append(position).append("]<").append(getState()).append(">"); return str.toString(); } 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 c0fbd9c3c251..53db0c5650ae 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 @@ -97,7 +97,7 @@ public abstract class TypeFlow { * the type flow graph. *

* A type flow can also be marked as saturated when one of its inputs has reached the saturated - * state and has propagated the "saturated" marker downstream. Thus, since in such a situtation + * state and has propagated the "saturated" marker downstream. Thus, since in such a situation * the input stops propagating type states, a flow's type state may be incomplete. It is up to * individual type flows to subscribe themselves directly to the type flows of their declared * types if they need further updates. @@ -358,7 +358,7 @@ public boolean addUse(BigBang bb, TypeFlow use) { private boolean addUse(BigBang bb, TypeFlow use, boolean propagateTypeState, boolean registerInput) { if (isSaturated() && propagateTypeState) { /* Let the use know that this flow is already saturated. */ - use.onInputSaturated(bb, this); + notifyUseOfSaturation(bb, use); return false; } if (doAddUse(bb, use, registerInput)) { @@ -370,7 +370,7 @@ private boolean addUse(BigBang bb, TypeFlow use, boolean propagateTypeState, * use would have missed the saturated signal. Let the use know that this flow * became saturated. */ - use.onInputSaturated(bb, this); + notifyUseOfSaturation(bb, use); /* And unlink the use. */ removeUse(use); return false; @@ -383,6 +383,10 @@ private boolean addUse(BigBang bb, TypeFlow use, boolean propagateTypeState, return false; } + protected void notifyUseOfSaturation(BigBang bb, TypeFlow use) { + use.onInputSaturated(bb, this); + } + protected boolean doAddUse(BigBang bb, TypeFlow use, boolean registerInput) { if (use.isSaturated()) { /* The use is already saturated so it will not be linked. */ @@ -420,14 +424,14 @@ public void addObserver(BigBang bb, TypeFlow observer) { private boolean addObserver(BigBang bb, TypeFlow observer, boolean triggerUpdate, boolean registerObservees) { if (isSaturated() && triggerUpdate) { /* Let the observer know that this flow is already saturated. */ - observer.onObservedSaturated(bb, this); + notifyObserverOfSaturation(bb, observer); return false; } if (doAddObserver(bb, observer, registerObservees)) { if (triggerUpdate) { if (isSaturated()) { /* This flow is already saturated, notify the observer. */ - observer.onObservedSaturated(bb, this); + notifyObserverOfSaturation(bb, observer); removeObserver(observer); return false; } else if (!this.state.isEmpty()) { @@ -449,6 +453,10 @@ public void run(DebugContext ignore) { return false; } + protected void notifyObserverOfSaturation(BigBang bb, TypeFlow observer) { + observer.onObservedSaturated(bb, this); + } + private boolean doAddObserver(BigBang bb, TypeFlow observer, boolean registerObservees) { /* * An observer is linked even if it is already saturated itself, hence no @@ -607,16 +615,24 @@ private void notifySaturated(BigBang bb) { /** This flow will swap itself out at all uses and observers. */ protected void swapOut(BigBang bb, TypeFlow newFlow) { for (TypeFlow use : getUses()) { - removeUse(use); - newFlow.addUse(bb, use); + swapAtUse(bb, newFlow, use); } for (TypeFlow observer : getObservers()) { - removeObserver(observer); - /* Notify the observer that its observed flow has changed. */ - observer.replacedObservedWith(bb, newFlow); + swapAtObserver(bb, newFlow, observer); } } + protected void swapAtUse(BigBang bb, TypeFlow newFlow, TypeFlow use) { + removeUse(use); + newFlow.addUse(bb, use); + } + + protected void swapAtObserver(BigBang bb, TypeFlow newFlow, TypeFlow observer) { + removeObserver(observer); + /* Notify the observer that its observed flow has changed. */ + observer.replacedObservedWith(bb, newFlow); + } + /** * Notified by an input that it is saturated and it will stop sending updates. */