Add NewUnboxedEnumInstance instruction for enum unboxing

Change-Id: Ieae19a988cfefe758610707fc1932af655a93405
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 64dd9a5..c6ba520 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.cf.code.CfNeg;
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfNewUnboxedEnum;
 import com.android.tools.r8.cf.code.CfNop;
 import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.cf.code.CfPosition;
@@ -503,6 +504,12 @@
     }
   }
 
+  public void print(CfNewUnboxedEnum newInstance) {
+    indent();
+    builder.append("newunboxedenum ");
+    appendClass(newInstance.getType());
+  }
+
   public void print(CfMultiANewArray multiANewArray) {
     indent();
     builder.append("multianewarray ");
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
new file mode 100644
index 0000000..89f8686
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2017, 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.ListIterator;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/** The class-file representation of {@link com.android.tools.r8.ir.code.NewUnboxedEnumInstance}. */
+public class CfNewUnboxedEnum extends CfInstruction implements CfTypeInstruction {
+
+  private final DexType type;
+  private final int ordinal;
+
+  public CfNewUnboxedEnum(DexType type, int ordinal) {
+    this.type = type;
+    this.ordinal = ordinal;
+  }
+
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
+  public DexType getType() {
+    return type;
+  }
+
+  @Override
+  public CfInstruction withType(DexType newType) {
+    return new CfNewUnboxedEnum(newType, ordinal);
+  }
+
+  @Override
+  public int getCompareToId() {
+    return Opcodes.NEW;
+  }
+
+  @Override
+  public int internalAcceptCompareTo(
+      CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
+    return type.acceptCompareTo(((CfNewUnboxedEnum) other).type, visitor);
+  }
+
+  @Override
+  public void write(
+      AppView<?> appView,
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerNewUnboxedEnumInstance(type);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addNewUnboxedEnumInstance(state.push(type).register, type, ordinal);
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+    return inliningConstraints.forNewUnboxedEnumInstance(type, context);
+  }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., objectref
+    frameBuilder.push(FrameType.initialized(type));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java
new file mode 100644
index 0000000..f9c26e6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2016, 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.code;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.nio.ShortBuffer;
+
+/** The dex representation of {@link com.android.tools.r8.ir.code.NewUnboxedEnumInstance}. */
+public class DexNewUnboxedEnumInstance extends Format21c<DexType> {
+
+  public static final int OPCODE = 0x22;
+  public static final String NAME = "NewUnboxedEnumInstance";
+  public static final String SMALI_NAME = "new-unboxed-enum-instance";
+
+  private final int ordinal;
+
+  public DexNewUnboxedEnumInstance(int AA, DexType BBBB, int ordinal) {
+    super(AA, BBBB);
+    this.ordinal = ordinal;
+  }
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+
+  @Override
+  public String getSmaliName() {
+    return SMALI_NAME;
+  }
+
+  @Override
+  public int getOpcode() {
+    throw new Unreachable();
+  }
+
+  @Override
+  void internalSubSpecify(StructuralSpecification<Format21c<DexType>, ?> spec) {
+    spec.withItem(i -> i.BBBB);
+  }
+
+  @Override
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void registerUse(UseRegistry registry) {
+    registry.registerNewUnboxedEnumInstance(getType());
+  }
+
+  public DexType getType() {
+    return BBBB;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder) {
+    builder.addNewUnboxedEnumInstance(AA, getType(), ordinal);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 708a02f..c9c0bc9 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -55,6 +55,10 @@
     registerTypeReference(type);
   }
 
+  public void registerNewUnboxedEnumInstance(DexType type) {
+    registerTypeReference(type);
+  }
+
   public abstract void registerStaticFieldRead(DexField field);
 
   public void registerStaticFieldReadFromMethodHandle(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
index eb26afa..c791889 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.NewUnboxedEnumInstance;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -153,5 +154,11 @@
       markInitializedOnNormalExit(instruction.clazz);
       return null;
     }
+
+    @Override
+    public Void visit(NewUnboxedEnumInstance instruction) {
+      assert false;
+      return null;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 708ffcf..3ac5e73b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1011,6 +1011,14 @@
     return null;
   }
 
+  public boolean isNewUnboxedEnumInstance() {
+    return false;
+  }
+
+  public NewUnboxedEnumInstance asNewUnboxedEnumInstance() {
+    return null;
+  }
+
   public boolean isNot() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
index a374439..8aa631d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -108,6 +108,8 @@
 
   T visit(NewInstance instruction);
 
+  T visit(NewUnboxedEnumInstance instruction);
+
   T visit(Not instruction);
 
   T visit(NumberConversion instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
new file mode 100644
index 0000000..8230766
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2016, 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.code;
+
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfNewUnboxedEnum;
+import com.android.tools.r8.code.DexNewUnboxedEnumInstance;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+
+/**
+ * Special instruction used by {@link com.android.tools.r8.ir.optimize.enums.EnumUnboxer}.
+ *
+ * <p>When applying the enum unboxer to the application, we move the class initializer of each
+ * unboxed enum to its utility class, and change each {@link NewInstance} instruction that
+ * instantiates the unboxed enum into a {@link NewUnboxedEnumInstance} that holds the ordinal of the
+ * enum instance.
+ *
+ * <p>The {@link NewUnboxedEnumInstance} is an instruction that produces an (initialized) instance
+ * of the unboxed enum, i.e., the out-type is a non-nullable class type. This is important for the
+ * code to type check until lens code rewriting, which replaces the {@link NewUnboxedEnumInstance}
+ * instructions by {@link ConstNumber} instructions.
+ *
+ * <p>Note: The {@link NewUnboxedEnumInstance} is only used from {@link
+ * com.android.tools.r8.ir.optimize.enums.EnumUnboxer#unboxEnums} until the execution of the {@link
+ * com.android.tools.r8.ir.conversion.PostMethodProcessor}. There should be no instances of {@link
+ * NewUnboxedEnumInstance} (nor {@link CfNewUnboxedEnum}, {@link DexNewUnboxedEnumInstance}) after
+ * IR processing has finished.
+ */
+public class NewUnboxedEnumInstance extends Instruction {
+
+  public final DexType clazz;
+  private final int ordinal;
+
+  public NewUnboxedEnumInstance(DexType clazz, int ordinal, Value dest) {
+    super(dest);
+    assert clazz != null;
+    this.clazz = clazz;
+    this.ordinal = ordinal;
+  }
+
+  public int getOrdinal() {
+    return ordinal;
+  }
+
+  public DexType getType() {
+    return clazz;
+  }
+
+  @Override
+  public int opcode() {
+    return Opcodes.NEW_UNBOXED_ENUM_INSTANCE;
+  }
+
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    int dest = builder.allocatedRegister(outValue(), getNumber());
+    builder.add(this, new DexNewUnboxedEnumInstance(dest, clazz, ordinal));
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + " " + clazz;
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    return other.isNewUnboxedEnumInstance() && other.asNewUnboxedEnumInstance().clazz == clazz;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    assert false : "NewUnboxedEnumInstance has no register arguments";
+    return 0;
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    return Constants.U8BIT_MAX;
+  }
+
+  @Override
+  public boolean instructionTypeCanThrow() {
+    // Depending on how this instruction is lowered to CF/DEX the instruction type may throw. If we
+    // lower the instruction to a const-number, then it can't throw, but if we lower it to something
+    // that triggers the class initialization of the enum utility class, then it could throw.
+    return true;
+  }
+
+  @Override
+  public boolean isNewUnboxedEnumInstance() {
+    return true;
+  }
+
+  @Override
+  public NewUnboxedEnumInstance asNewUnboxedEnumInstance() {
+    return this;
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, ProgramMethod context) {
+    return inliningConstraints.forNewUnboxedEnumInstance(clazz, context);
+  }
+
+  @Override
+  public boolean hasInvariantOutType() {
+    return true;
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.storeOutValue(this, it);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfNewUnboxedEnum(clazz, ordinal));
+  }
+
+  @Override
+  public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
+    return clazz;
+  }
+
+  @Override
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(clazz, Nullability.definitelyNotNull(), appView);
+  }
+
+  @Override
+  public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
+    // Conservatively return true.
+    return true;
+  }
+
+  @Override
+  public boolean verifyTypes(AppView<?> appView, VerifyTypesHelper verifyTypesHelper) {
+    TypeElement type = getOutType();
+    assert type.isClassType();
+    assert type.asClassType().getClassType() == clazz || appView.options().testing.allowTypeErrors;
+    assert type.isDefinitelyNotNull();
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index 51b19d8e..d2c79d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -56,22 +56,23 @@
   int NEW_ARRAY_EMPTY = 47;
   int NEW_ARRAY_FILLED_DATA = 48;
   int NEW_INSTANCE = 49;
-  int NOT = 50;
-  int NUMBER_CONVERSION = 51;
-  int OR = 52;
-  int POP = 53;
-  int REM = 54;
-  int RETURN = 55;
-  int SHL = 56;
-  int SHR = 57;
-  int STATIC_GET = 58;
-  int STATIC_PUT = 59;
-  int STORE = 60;
-  int STRING_SWITCH = 61;
-  int SUB = 62;
-  int SWAP = 63;
-  int THROW = 64;
-  int USHR = 65;
-  int XOR = 66;
-  int UNINITIALIZED_THIS_LOCAL_READ = 67;
+  int NEW_UNBOXED_ENUM_INSTANCE = 50;
+  int NOT = 51;
+  int NUMBER_CONVERSION = 52;
+  int OR = 53;
+  int POP = 54;
+  int REM = 55;
+  int RETURN = 56;
+  int SHL = 57;
+  int SHR = 58;
+  int STATIC_GET = 59;
+  int STATIC_PUT = 60;
+  int STORE = 61;
+  int STRING_SWITCH = 62;
+  int SUB = 63;
+  int SWAP = 64;
+  int THROW = 65;
+  int USHR = 66;
+  int XOR = 67;
+  int UNINITIALIZED_THIS_LOCAL_READ = 68;
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index ba1a15c..2563fa8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -91,6 +91,7 @@
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.NewUnboxedEnumInstance;
 import com.android.tools.r8.ir.code.Not;
 import com.android.tools.r8.ir.code.NumberConversion;
 import com.android.tools.r8.ir.code.NumberGenerator;
@@ -1813,6 +1814,14 @@
     addInstruction(instruction);
   }
 
+  public void addNewUnboxedEnumInstance(int dest, DexType type, int ordinal) {
+    TypeElement instanceType = TypeElement.fromDexType(type, definitelyNotNull(), appView);
+    Value out = writeRegister(dest, instanceType, ThrowingInfo.CAN_THROW);
+    NewUnboxedEnumInstance instruction = new NewUnboxedEnumInstance(type, ordinal, out);
+    assert instruction.instructionTypeCanThrow();
+    addInstruction(instruction);
+  }
+
   public void addReturn(int value) {
     DexType returnType = method.getDefinition().returnType();
     if (returnType.isVoidType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index f44ec67..70a2316 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -26,6 +26,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.MOVE_EXCEPTION;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_EMPTY;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
+import static com.android.tools.r8.ir.code.Opcodes.NEW_UNBOXED_ENUM_INSTANCE;
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
@@ -590,6 +591,9 @@
             }
             break;
 
+          case NEW_UNBOXED_ENUM_INSTANCE:
+            break;
+
           case RETURN:
             {
               Return ret = current.asReturn();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index e00c267..17db09c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -292,6 +292,10 @@
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
+  public ConstraintWithTarget forNewUnboxedEnumInstance(DexType type, ProgramMethod context) {
+    return ConstraintWithTarget.ALWAYS;
+  }
+
   public ConstraintWithTarget forAssume() {
     return ConstraintWithTarget.ALWAYS;
   }