Support static field analysis for enum pinned fields
Bug: 172528424
Change-Id: I8fe2a9739116af9e90759933b5fbcb418f7e5588
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 8e52af1..ee985c4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DominatorTree;
@@ -86,10 +87,16 @@
return null;
}
+ boolean isStaticFieldValueAnalysis() {
+ return false;
+ }
+
StaticFieldValueAnalysis asStaticFieldValueAnalysis() {
return null;
}
+ abstract boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field);
+
abstract boolean isSubjectToOptimization(DexEncodedField field);
void recordFieldPut(DexEncodedField field, Instruction instruction) {
@@ -118,9 +125,18 @@
if (instruction.isFieldPut()) {
FieldInstruction fieldPut = instruction.asFieldInstruction();
DexField field = fieldPut.getField();
- DexEncodedField encodedField = appInfo.resolveField(field).getResolvedField();
- if (encodedField != null && isSubjectToOptimization(encodedField)) {
- recordFieldPut(encodedField, fieldPut);
+ SuccessfulFieldResolutionResult fieldResolutionResult =
+ appInfo.resolveField(field).asSuccessfulResolution();
+ if (fieldResolutionResult != null) {
+ DexEncodedField encodedField = fieldResolutionResult.getResolvedField();
+ assert encodedField != null;
+ if (isSubjectToOptimization(encodedField)) {
+ recordFieldPut(encodedField, fieldPut);
+ } else if (isStaticFieldValueAnalysis()
+ && fieldResolutionResult.getResolvedHolder().isEnum()
+ && isSubjectToOptimizationIgnoringPinning(encodedField)) {
+ recordFieldPut(encodedField, fieldPut);
+ }
}
} else if (isInstanceFieldValueAnalysis()
&& instruction.isInvokeConstructor(appView.dexItemFactory())) {
@@ -153,15 +169,15 @@
boolean priorReadsWillReadSameValue =
!classInitializerDefaultsResult.hasStaticValue(field) && fieldPut.value().isZero();
if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(field, fieldPut)) {
- if (!isInstanceFieldValueAnalysis()) {
+ // TODO(b/172528424): Generalize to InstanceFieldValueAnalysis.
+ if (isStaticFieldValueAnalysis()) {
// At this point the value read in the field can be only the default static value, if read
// prior to the put, or the value put, if read after the put. We still want to record it
// because the default static value is typically null/0, so code present after a null/0
// check can take advantage of the optimization.
DexValue valueBeforePut = classInitializerDefaultsResult.getStaticValue(field);
asStaticFieldValueAnalysis()
- .updateFieldOptimizationInfoWith2Values(
- field, fieldPut, fieldPut.value(), valueBeforePut);
+ .updateFieldOptimizationInfoWith2Values(field, fieldPut.value(), valueBeforePut);
}
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index cc607ac..c4061ec 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -124,6 +124,11 @@
}
@Override
+ boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
+ throw new Unreachable("Used by static analysis only.");
+ }
+
+ @Override
void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
if (fieldNeverWrittenBetweenInstancePutAndMethodExit(field, fieldPut.asInstancePut())) {
recordInstanceFieldIsInitializedWithValue(field, value);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 3ab1305..03455fb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -42,12 +43,15 @@
public class StaticFieldValueAnalysis extends FieldValueAnalysis {
+ private final StaticFieldValues.Builder builder;
+
private StaticFieldValueAnalysis(
AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
super(appView, code, feedback);
+ builder = StaticFieldValues.builder(code.context().getHolder());
}
- public static void run(
+ public static StaticFieldValues run(
AppView<?> appView,
IRCode code,
ClassInitializerDefaultsResult classInitializerDefaultsResult,
@@ -57,9 +61,16 @@
assert appView.enableWholeProgramOptimizations();
assert code.context().getDefinition().isClassInitializer();
timing.begin("Analyze class initializer");
- new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
- .computeFieldOptimizationInfo(classInitializerDefaultsResult);
+ StaticFieldValues result =
+ new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
+ .analyze(classInitializerDefaultsResult, code.context().getHolderType());
timing.end();
+ return result;
+ }
+
+ @Override
+ boolean isStaticFieldValueAnalysis() {
+ return true;
}
@Override
@@ -67,6 +78,12 @@
return this;
}
+ StaticFieldValues analyze(
+ ClassInitializerDefaultsResult classInitializerDefaultsResult, DexType holderType) {
+ computeFieldOptimizationInfo(classInitializerDefaultsResult);
+ return builder.build();
+ }
+
@Override
void computeFieldOptimizationInfo(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
super.computeFieldOptimizationInfo(classInitializerDefaultsResult);
@@ -105,14 +122,32 @@
}
@Override
- void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
- // Abstract value.
- feedback.recordFieldHasAbstractValue(field, appView, getOrComputeAbstractValue(value, field));
-
- setDynamicType(field, value, false);
+ boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
+ return field.isStatic()
+ && field.getHolderType() == context.getHolderType()
+ && appView
+ .appInfo()
+ .isFieldOnlyWrittenInMethodIgnoringPinning(field, context.getDefinition());
}
- private void setDynamicType(DexEncodedField field, Value value, boolean maybeNull) {
+ @Override
+ void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
+ AbstractValue abstractValue = getOrComputeAbstractValue(value, field);
+ updateFieldOptimizationInfo(field, value, abstractValue, false);
+ }
+
+ void updateFieldOptimizationInfo(
+ DexEncodedField field, Value value, AbstractValue abstractValue, boolean maybeNull) {
+ builder.recordStaticField(field, abstractValue, appView.dexItemFactory());
+
+ // We cannot modify FieldOptimizationInfo of pinned fields.
+ if (appView.appInfo().isPinned(field)) {
+ return;
+ }
+
+ // Abstract value.
+ feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+
// Dynamic upper bound type.
TypeElement fieldType =
TypeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
@@ -137,16 +172,16 @@
}
public void updateFieldOptimizationInfoWith2Values(
- DexEncodedField field, FieldInstruction fieldPut, Value valuePut, DexValue valueBeforePut) {
+ DexEncodedField field, Value valuePut, DexValue valueBeforePut) {
// We are interested in the AbstractValue only if it's null or a value, so we can use the value
// if the code is protected by a null check.
if (valueBeforePut != DexValueNull.NULL) {
return;
}
- feedback.recordFieldHasAbstractValue(
- field, appView, NullOrAbstractValue.create(getOrComputeAbstractValue(valuePut, field)));
- setDynamicType(field, valuePut, true);
+ AbstractValue abstractValue =
+ NullOrAbstractValue.create(getOrComputeAbstractValue(valuePut, field));
+ updateFieldOptimizationInfo(field, valuePut, abstractValue, true);
}
private AbstractValue getOrComputeAbstractValue(Value value, DexEncodedField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
new file mode 100644
index 0000000..8a02e12
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.google.common.collect.ImmutableMap;
+
+public abstract class StaticFieldValues {
+
+ public boolean isEnumStaticFieldValues() {
+ return false;
+ }
+
+ public EnumStaticFieldValues asEnumStaticFieldValues() {
+ return null;
+ }
+
+ public static Builder builder(DexProgramClass clazz) {
+ return clazz.isEnum() ? EnumStaticFieldValues.builder() : EmptyStaticValues.builder();
+ }
+
+ public abstract static class Builder {
+
+ public abstract void recordStaticField(
+ DexEncodedField staticField, AbstractValue value, DexItemFactory factory);
+
+ public abstract StaticFieldValues build();
+ }
+
+ // All the abstract values stored here may match a pinned field, using them requires therefore
+ // to check the field is not pinned or prove it is no longer pinned.
+ public static class EnumStaticFieldValues extends StaticFieldValues {
+ private final ImmutableMap<DexField, AbstractValue> enumAbstractValues;
+ private final DexField valuesField;
+ private final AbstractValue valuesAbstractValue;
+
+ public EnumStaticFieldValues(
+ ImmutableMap<DexField, AbstractValue> enumAbstractValues,
+ DexField valuesField,
+ AbstractValue valuesAbstractValue) {
+ this.enumAbstractValues = enumAbstractValues;
+ this.valuesField = valuesField;
+ this.valuesAbstractValue = valuesAbstractValue;
+ }
+
+ static StaticFieldValues.Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder extends StaticFieldValues.Builder {
+ private final ImmutableMap.Builder<DexField, AbstractValue> enumAbstractValuesBuilder =
+ ImmutableMap.builder();
+ private DexField valuesFields;
+ private AbstractValue valuesAbstractValue;
+
+ Builder() {}
+
+ @Override
+ public void recordStaticField(
+ DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
+ // TODO(b/166532388): Stop relying on the values name.
+ if (staticField.getName() == factory.enumValuesFieldName) {
+ valuesFields = staticField.field;
+ valuesAbstractValue = value;
+ } else if (staticField.isEnum()) {
+ enumAbstractValuesBuilder.put(staticField.field, value);
+ }
+ }
+
+ @Override
+ public StaticFieldValues build() {
+ ImmutableMap<DexField, AbstractValue> enumAbstractValues =
+ enumAbstractValuesBuilder.build();
+ if (valuesAbstractValue == null && enumAbstractValues.isEmpty()) {
+ return EmptyStaticValues.getInstance();
+ }
+ return new EnumStaticFieldValues(enumAbstractValues, valuesFields, valuesAbstractValue);
+ }
+ }
+
+ @Override
+ public boolean isEnumStaticFieldValues() {
+ return true;
+ }
+
+ @Override
+ public EnumStaticFieldValues asEnumStaticFieldValues() {
+ return this;
+ }
+
+ // We need to access the enum instance object state to figure out if it contains known constant
+ // field values. The enum instance may be accessed in two ways, directly through the enum
+ // static field, or through the enum $VALUES field. If none of them are kept, the instance is
+ // effectively unused. The object state may be stored in the enum static field optimization
+ // info, if kept, or in the $VALUES optimization info, if kept.
+ // If the enum instance is unused, this method answers null.
+ public ObjectState getObjectStateForPossiblyPinnedEnumInstance(DexField field, int ordinal) {
+
+ // Attempt 1: Get object state from the instance field's optimization info.
+ AbstractValue fieldValue = enumAbstractValues.get(field);
+ if (fieldValue != null) {
+ if (fieldValue.isSingleFieldValue()) {
+ return fieldValue.asSingleFieldValue().getState();
+ }
+ if (fieldValue.isUnknown()) {
+ return ObjectState.empty();
+ }
+ assert fieldValue.isZero();
+ }
+
+ // Attempt 2: Get object state from the values field's optimization info.
+ if (valuesAbstractValue.isZero()) {
+ // Unused enum instance.
+ return null;
+ }
+ if (valuesAbstractValue.isUnknown()) {
+ return ObjectState.empty();
+ }
+ assert valuesAbstractValue.isSingleFieldValue();
+ ObjectState valuesState = this.valuesAbstractValue.asSingleFieldValue().getState();
+ if (valuesState.isEnumValuesObjectState()) {
+ return valuesState.asEnumValuesObjectState().getObjectStateForOrdinal(ordinal);
+ }
+ return ObjectState.empty();
+ }
+ }
+
+ public static class EmptyStaticValues extends StaticFieldValues {
+ private static EmptyStaticValues INSTANCE = new EmptyStaticValues();
+
+ private EmptyStaticValues() {}
+
+ public static EmptyStaticValues getInstance() {
+ return INSTANCE;
+ }
+
+ static StaticFieldValues.Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder extends StaticFieldValues.Builder {
+
+ @Override
+ public void recordStaticField(
+ DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
+ // Do nothing.
+ }
+
+ @Override
+ public StaticFieldValues build() {
+ return EmptyStaticValues.getInstance();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 2ef58cd..e340861 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
@@ -1708,16 +1709,21 @@
}
InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos = null;
+ StaticFieldValues staticFieldValues = null;
if (method.getDefinition().isInitializer()) {
if (method.getDefinition().isClassInitializer()) {
- StaticFieldValueAnalysis.run(
- appView, code, classInitializerDefaultsResult, feedback, timing);
+ staticFieldValues =
+ StaticFieldValueAnalysis.run(
+ appView, code, classInitializerDefaultsResult, feedback, timing);
} else {
instanceFieldInitializationInfos =
InstanceFieldValueAnalysis.run(
appView, code, classInitializerDefaultsResult, feedback, timing);
}
}
+ if (enumUnboxer != null) {
+ enumUnboxer.recordEnumState(method.getHolder(), staticFieldValues);
+ }
methodOptimizationInfoCollector.collectMethodOptimizationInfo(
method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos, timing);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 2d46582..2d436ad 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -28,6 +28,8 @@
import com.android.tools.r8.graph.ProgramPackageCollection;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -86,6 +88,8 @@
private final EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo;
private final ProgramPackageCollection enumsToUnboxWithPackageRequirement =
ProgramPackageCollection.createEmpty();
+ private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
+ new ConcurrentHashMap<>();
private EnumUnboxingRewriter enumUnboxerRewriter;
@@ -453,6 +457,7 @@
}
builder.put(enumClass.type, typeBuilder.build());
});
+ staticFieldValuesMap.clear();
return new EnumInstanceFieldDataMap(builder.build());
}
@@ -494,6 +499,17 @@
return useRegistry.computeConstraint(method.asProgramMethod(appView));
}
+ public void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues) {
+ if (staticFieldValues == null || !staticFieldValues.isEnumStaticFieldValues()) {
+ return;
+ }
+ assert clazz.isEnum();
+ EnumStaticFieldValues enumStaticFieldValues = staticFieldValues.asEnumStaticFieldValues();
+ if (getEnumUnboxingCandidateOrNull(clazz.type) != null) {
+ staticFieldValuesMap.put(clazz.type, enumStaticFieldValues);
+ }
+ }
+
private class EnumAccessibilityUseRegistry extends UseRegistry {
private ProgramMethod context;
@@ -939,8 +955,13 @@
EnumValueInfoMapCollection.EnumValueInfoMap enumValueInfoMap =
appView.appInfo().getEnumValueInfoMap(enumClass.type);
for (DexField staticField : enumValueInfoMap.enumValues()) {
+ EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type);
+ if (enumStaticFieldValues == null) {
+ return EnumInstanceFieldUnknownData.getInstance();
+ }
ObjectState enumInstanceState =
- computeEnumInstanceObjectState(enumClass, staticField, enumValueInfoMap);
+ enumStaticFieldValues.getObjectStateForPossiblyPinnedEnumInstance(
+ staticField, enumValueInfoMap.getEnumValueInfo(staticField).ordinal);
if (enumInstanceState == null) {
// The enum instance is effectively unused. No need to generate anything for it, the path
// will never be taken.
@@ -966,52 +987,6 @@
return new EnumInstanceFieldMappingData(data);
}
- // We need to access the enum instance object state to figure out if it contains known constant
- // field values. The enum instance may be accessed in two ways, directly through the enum
- // static field, or through the enum $VALUES field. If none of them are kept, the instance is
- // effectively unused. The object state may be stored in the enum static field optimization
- // info, if kept, or in the $VALUES optimization info, if kept.
- // If the enum instance is unused, this method answers null.
- private ObjectState computeEnumInstanceObjectState(
- DexProgramClass enumClass,
- DexField staticField,
- EnumValueInfoMapCollection.EnumValueInfoMap enumValueInfoMap) {
- // Attempt 1: Get object state from the instance field's optimization info.
- DexEncodedField encodedStaticField = enumClass.lookupStaticField(staticField);
- AbstractValue enumInstanceValue = encodedStaticField.getOptimizationInfo().getAbstractValue();
- if (enumInstanceValue.isSingleFieldValue()) {
- return enumInstanceValue.asSingleFieldValue().getState();
- }
- if (enumInstanceValue.isUnknown()) {
- return ObjectState.empty();
- }
- assert enumInstanceValue.isZero();
-
- // Attempt 2: Get object state from the values field's optimization info.
- DexEncodedField valuesField =
- enumClass.lookupStaticField(
- factory.createField(
- enumClass.type,
- factory.createArrayType(1, enumClass.type),
- factory.enumValuesFieldName));
- AbstractValue valuesValue = valuesField.getOptimizationInfo().getAbstractValue();
- if (valuesValue.isZero()) {
- // Unused enum instance.
- return null;
- }
- if (valuesValue.isUnknown()) {
- return ObjectState.empty();
- }
- assert valuesValue.isSingleFieldValue();
- ObjectState valuesState = valuesValue.asSingleFieldValue().getState();
- if (valuesState.isEnumValuesObjectState()) {
- return valuesState
- .asEnumValuesObjectState()
- .getObjectStateForOrdinal(enumValueInfoMap.getEnumValueInfo(staticField).ordinal);
- }
- return ObjectState.empty();
- }
-
private void reportEnumsAnalysis() {
assert debugLogEnabled;
Reporter reporter = appView.options().reporter;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 7687ad4..c5adef8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -803,6 +803,13 @@
if (isPinned(field.field)) {
return false;
}
+ return isFieldOnlyWrittenInMethodIgnoringPinning(field, method);
+ }
+
+ public boolean isFieldOnlyWrittenInMethodIgnoringPinning(
+ DexEncodedField field, DexEncodedMethod method) {
+ assert checkIfObsolete();
+ assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.field);
return fieldAccessInfo != null
&& fieldAccessInfo.isWritten()