Introduce a builder for NonTrivialInstanceInitializerInfo

Change-Id: I84e7cddc9d9985c34b0bc7b94545399a55cf7e91
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index ee49c97..eac8791 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -5,6 +5,16 @@
 
 import static com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query.DIRECTLY;
 import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.GOTO;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -392,79 +402,78 @@
       // Defining a finalize method can observe the side-effect of Object.<init> GC registration.
       return null;
     }
+    NonTrivialInstanceInitializerInfo.Builder builder = NonTrivialInstanceInitializerInfo.builder();
     Value receiver = code.getThis();
-    for (Instruction insn : code.instructions()) {
-      if (insn.isReturn()) {
-        continue;
-      }
+    for (Instruction instruction : code.instructions()) {
+      switch (instruction.opcode()) {
+        case ARGUMENT:
+        case ASSUME:
+        case CONST_NUMBER:
+        case RETURN:
+          break;
 
-      if (insn.isAssume()) {
-        continue;
-      }
-
-      if (insn.isArgument()) {
-        continue;
-      }
-
-      if (insn.isConstInstruction()) {
-        if (insn.instructionInstanceCanThrow()) {
-          return null;
-        } else {
-          continue;
-        }
-      }
-
-      if (insn.isInvokeDirect()) {
-        InvokeDirect invokedDirect = insn.asInvokeDirect();
-        DexMethod invokedMethod = invokedDirect.getInvokedMethod();
-        if (!dexItemFactory.isConstructor(invokedMethod)) {
-          return null;
-        }
-        if (invokedMethod.holder != clazz.superType) {
-          return null;
-        }
-        // java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
-        if (invokedMethod == dexItemFactory.enumMethods.constructor
-            || invokedMethod == dexItemFactory.objectMethods.constructor) {
-          continue;
-        }
-        DexEncodedMethod callTarget = appView.definitionFor(invokedMethod);
-        if (callTarget == null
-            || callTarget.getOptimizationInfo().getInstanceInitializerInfo().isDefaultInfo()
-            || invokedDirect.getReceiver() != receiver) {
-          return null;
-        }
-        for (Value value : invokedDirect.inValues()) {
-          if (value != receiver && !(value.isConstant() || value.isArgument())) {
+        case CONST_CLASS:
+        case CONST_STRING:
+        case DEX_ITEM_BASED_CONST_STRING:
+          if (instruction.instructionMayTriggerMethodInvocation(appView, clazz.type)) {
             return null;
           }
-        }
-        continue;
-      }
+          break;
 
-      if (insn.isInstancePut()) {
-        InstancePut instancePut = insn.asInstancePut();
-        DexEncodedField field = appView.appInfo().resolveFieldOn(clazz, instancePut.getField());
-        if (field == null
-            || instancePut.object() != receiver
-            || (instancePut.value() != receiver && !instancePut.value().isArgument())) {
+        case GOTO:
+          // Trivial goto to the next block.
+          if (!instruction.asGoto().isTrivialGotoToTheNextBlock(code)) {
+            return null;
+          }
+          break;
+
+        case INSTANCE_PUT:
+          {
+            InstancePut instancePut = instruction.asInstancePut();
+            DexEncodedField field = appView.appInfo().resolveFieldOn(clazz, instancePut.getField());
+            if (field == null
+                || instancePut.object() != receiver
+                || (instancePut.value() != receiver && !instancePut.value().isArgument())) {
+              return null;
+            }
+          }
+          break;
+
+        case INVOKE_DIRECT:
+          {
+            InvokeDirect invoke = instruction.asInvokeDirect();
+            DexMethod invokedMethod = invoke.getInvokedMethod();
+            if (!dexItemFactory.isConstructor(invokedMethod)) {
+              return null;
+            }
+            if (invokedMethod.holder != clazz.superType) {
+              return null;
+            }
+            // java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
+            if (invokedMethod == dexItemFactory.enumMethods.constructor
+                || invokedMethod == dexItemFactory.objectMethods.constructor) {
+              break;
+            }
+            DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
+            if (singleTarget == null
+                || singleTarget.getOptimizationInfo().getInstanceInitializerInfo().isDefaultInfo()
+                || invoke.getReceiver() != receiver) {
+              return null;
+            }
+            for (Value value : invoke.inValues()) {
+              if (value != receiver && !(value.isConstant() || value.isArgument())) {
+                return null;
+              }
+            }
+          }
+          break;
+
+        default:
+          // Other instructions make the instance initializer not eligible.
           return null;
-        }
-        continue;
       }
-
-      if (insn.isGoto()) {
-        // Trivial goto to the next block.
-        if (insn.asGoto().isTrivialGotoToTheNextBlock(code)) {
-          continue;
-        }
-        return null;
-      }
-
-      // Other instructions make the instance initializer not eligible.
-      return null;
     }
-    return NonTrivialInstanceInitializerInfo.INSTANCE;
+    return builder.build();
   }
 
   private void identifyInvokeSemanticsForInlining(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index fa41621..7802e12 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -4,33 +4,108 @@
 
 package com.android.tools.r8.ir.optimize.info.initializer;
 
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
 
 public final class NonTrivialInstanceInitializerInfo extends InstanceInitializerInfo {
 
-  public static final NonTrivialInstanceInitializerInfo INSTANCE =
-      new NonTrivialInstanceInitializerInfo();
+  private static final int INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT = 1 << 0;
+  private static final int NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS = 1 << 1;
+  private static final int RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN = 1 << 2;
 
-  private NonTrivialInstanceInitializerInfo() {}
+  private final int data;
+  private final AbstractFieldSet readSet;
+
+  private NonTrivialInstanceInitializerInfo(int data, AbstractFieldSet readSet) {
+    assert verifyNoUnknownBits(data);
+    this.data = data;
+    this.readSet = readSet;
+  }
+
+  private static boolean verifyNoUnknownBits(int data) {
+    int knownBits =
+        INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT
+            | NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS
+            | RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN;
+    assert (data & ~knownBits) == 0;
+    return true;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
 
   @Override
   public AbstractFieldSet readSet() {
-    return EmptyFieldSet.getInstance();
+    return readSet;
   }
 
   @Override
   public boolean instanceFieldInitializationMayDependOnEnvironment() {
-    return false;
+    return (data & INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT) == 0;
   }
 
   @Override
   public boolean mayHaveOtherSideEffectsThanInstanceFieldAssignments() {
-    return false;
+    return (data & NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS) == 0;
   }
 
   @Override
   public boolean receiverNeverEscapesOutsideConstructorChain() {
-    return true;
+    return (data & RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN) != 0;
+  }
+
+  public static class Builder {
+
+    private int data =
+        INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT
+            | NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS
+            | RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN;
+    private AbstractFieldSet readSet = EmptyFieldSet.getInstance();
+
+    private boolean isTrivial() {
+      return data == 0 && readSet.isTop();
+    }
+
+    public Builder markFieldAsRead(DexEncodedField field) {
+      if (readSet.isKnownFieldSet()) {
+        if (readSet.isBottom()) {
+          readSet = new ConcreteMutableFieldSet(field);
+        } else {
+          readSet.asConcreteFieldSet().add(field);
+        }
+      }
+      assert readSet.contains(field);
+      return this;
+    }
+
+    public Builder markAllFieldsAsRead() {
+      readSet = UnknownFieldSet.getInstance();
+      return this;
+    }
+
+    public Builder setInstanceFieldInitializationMayDependOnEnvironment() {
+      data &= ~INSTANCE_FIELD_INITIALIZATION_INDEPENDENT_OF_ENVIRONMENT;
+      return this;
+    }
+
+    public Builder setMayHaveOtherSideEffectsThanInstanceFieldAssignments() {
+      data &= ~NO_OTHER_SIDE_EFFECTS_THAN_INSTANCE_FIELD_ASSIGNMENTS;
+      return this;
+    }
+
+    public Builder setReceiverMayEscapeOutsideConstructorChain() {
+      data &= ~RECEIVER_NEVER_ESCAPE_OUTSIDE_CONSTRUCTOR_CHAIN;
+      return this;
+    }
+
+    public InstanceInitializerInfo build() {
+      return isTrivial()
+          ? DefaultInstanceInitializerInfo.getInstance()
+          : new NonTrivialInstanceInitializerInfo(data, readSet);
+    }
   }
 }