Merge "[CF] Implement simple store/load elimination and avoid storing constants."
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 5ddca3f..07c7c53 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.utils.InternalOptions;
 
 public class ConstString extends ConstInstruction {
 
@@ -82,6 +83,12 @@
   }
 
   @Override
+  public boolean canBeDeadCode(IRCode code, InternalOptions options) {
+    // The const-string instruction is a throwing instruction in DEX, but not so in CF.
+    return options.outputClassFiles;
+  }
+
+  @Override
   public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
     stack.storeOutValue(this, it);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 4d9c67a..ce990c1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -3,15 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeStaticRange;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import java.util.List;
+import org.objectweb.asm.Opcodes;
 
 public class InvokeStatic extends InvokeMethod {
 
@@ -86,4 +89,9 @@
   public InlineAction computeInlining(InliningOracle decider) {
     return decider.computeForInvokeStatic(this);
   }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfInvoke(Opcodes.INVOKESTATIC, getInvokedMethod()));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 28d4165..734b2dd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -44,6 +44,6 @@
   @Override
   public void buildCf(CfBuilder builder) {
     Value value = inValues.get(0);
-    builder.add(new CfLoad(value.outType(), 2 * value.getNumber()));
+    builder.add(new CfLoad(value.outType(), builder.getLocalRegister(value)));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 2a22f28..ddc4f50 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -43,6 +43,6 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfStore(outType(), 2 * outValue.getNumber()));
+    builder.add(new CfStore(outType(), builder.getLocalRegister(outValue)));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 8c00fcc..aed20acb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -5,12 +5,16 @@
 
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.ConstInstruction;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
@@ -21,6 +25,11 @@
 import com.android.tools.r8.ir.code.Store;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.utils.InternalOptions;
+import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -30,27 +39,42 @@
   private final DexEncodedMethod method;
   private final IRCode code;
   private List<CfInstruction> instructions;
+  private Reference2IntMap<Value> argumentRegisters;
+  private int maxLocals;
 
   public static class StackHelper {
 
-    public final DexMethod method;
-
-    public StackHelper(DexMethod method) {
-      this.method = method;
-    }
-
     public void loadInValues(Instruction instruction, InstructionListIterator it) {
       it.previous();
       for (int i = 0; i < instruction.inValues().size(); i++) {
         Value value = instruction.inValues().get(i);
         StackValue stackValue = new StackValue(value.outType());
+        Instruction load;
+        if (value.isConstant()) {
+          ConstInstruction constant = value.getConstInstruction();
+          if (constant.isConstNumber()) {
+            load = new ConstNumber(stackValue, constant.asConstNumber().getRawValue());
+          } else if (constant.isConstString()) {
+            load = new ConstString(stackValue, constant.asConstString().getValue());
+          } else if (constant.isConstClass()) {
+            load = new ConstClass(stackValue, constant.asConstClass().getValue());
+          } else {
+            throw new Unreachable("Unexpected constant value: " + value);
+          }
+        } else {
+          load = new Load(stackValue, value);
+        }
+        add(load, instruction, it);
+        value.removeUser(instruction);
         instruction.replaceValue(value, stackValue);
-        add(new Load(stackValue, value), instruction, it);
       }
       it.next();
     }
 
     public void storeOutValue(Instruction instruction, InstructionListIterator it) {
+      if (instruction.isOutConstant()) {
+        return;
+      }
       StackValue newOutValue = new StackValue(instruction.outType());
       Value oldOutValue = instruction.swapOutValue(newOutValue);
       add(new Store(oldOutValue, newOutValue), instruction, it);
@@ -75,11 +99,12 @@
     this.code = code;
   }
 
-  public Code build() {
+  public Code build(CodeRewriter rewriter, InternalOptions options) {
     try {
       loadStoreInsertion();
-      // TODO(zerny): Optimize load/store patterns.
-      // TODO(zerny): Compute locals/register allocation.
+      DeadCodeRemover.removeDeadCode(code, rewriter, options);
+      removeUnneededLoadsAndStores();
+      allocateLocalRegisters();
       // TODO(zerny): Compute debug info.
       return buildCfCode();
     } catch (Unimplemented e) {
@@ -89,7 +114,7 @@
   }
 
   private void loadStoreInsertion() {
-    StackHelper stack = new StackHelper(method.method);
+    StackHelper stack = new StackHelper();
     for (BasicBlock block : code.blocks) {
       InstructionListIterator it = block.listIterator();
       while (it.hasNext()) {
@@ -99,8 +124,55 @@
     }
   }
 
+  private void removeUnneededLoadsAndStores() {
+    Iterator<BasicBlock> blockIterator = code.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator it = block.listIterator();
+      while (it.hasNext()) {
+        Instruction store = it.next();
+        if (store instanceof Store && store.outValue().numberOfAllUsers() == 1) {
+          Instruction load = it.peekNext();
+          if (load instanceof Load && load.inValues().get(0) == store.outValue()) {
+            Value storeIn = store.inValues().get(0);
+            Value loadOut = load.outValue();
+            loadOut.replaceUsers(storeIn);
+            storeIn.removeUser(store);
+            store.outValue().removeUser(load);
+            // Remove the store.
+            it.previous();
+            it.remove();
+            // Remove the load.
+            it.next();
+            it.remove();
+          }
+        }
+      }
+    }
+  }
+
+  private void allocateLocalRegisters() {
+    // TODO(zerny): Allocate locals based on live ranges.
+    InstructionIterator it = code.instructionIterator();
+    argumentRegisters = new Reference2IntArrayMap<>(
+        method.method.proto.parameters.values.length + (method.accessFlags.isStrict() ? 0 : 1));
+    int argumentRegister = 0;
+    int maxRegister = -1;
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      if (instruction.isArgument()) {
+        argumentRegisters.put(instruction.outValue(), argumentRegister);
+        argumentRegister += instruction.outValue().requiredRegisters();
+        maxRegister = argumentRegister - 1;
+      } else if (instruction.outValue() != null
+          && !(instruction.outValue() instanceof StackValue)) {
+        maxRegister = Math.max(maxRegister, 2 * instruction.outValue().getNumber());
+      }
+    }
+    maxLocals = maxRegister + 1;
+  }
+
   private CfCode buildCfCode() {
-    int maxLocalNumber = -1;
     int maxStack = 0;
     int currentStack = 0;
     instructions = new ArrayList<>();
@@ -113,26 +185,31 @@
         if (instruction.outValue() != null) {
           Value outValue = instruction.outValue();
           if (outValue instanceof StackValue) {
-            ++currentStack;
+            currentStack += outValue.requiredRegisters();
             maxStack = Math.max(maxStack, currentStack);
-          } else {
-            maxLocalNumber = Math.max(maxLocalNumber, outValue.getNumber());
           }
         }
         for (Value inValue : instruction.inValues()) {
           if (inValue instanceof StackValue) {
-            --currentStack;
+            currentStack -= inValue.requiredRegisters();
           }
         }
         instruction.buildCf(this);
       }
     }
     assert currentStack == 0;
-    return new CfCode(maxStack, 2 * (maxLocalNumber + 1), instructions);
+    return new CfCode(maxStack, maxLocals, instructions);
   }
 
   // Callbacks
 
+  public int getLocalRegister(Value value) {
+    if (value.isArgument()) {
+      return argumentRegisters.getInt(value);
+    }
+    return 2 * value.getNumber();
+  }
+
   public void add(CfInstruction instruction) {
     instructions.add(instruction);
   }
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 e31c592..0151b1a 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
@@ -590,7 +590,7 @@
     assert method.getCode().isJarCode();
     CfBuilder builder = new CfBuilder(method, code);
     // TODO(zerny): Change the return type of CfBuilder::build CfCode once complete.
-    Code result = builder.build();
+    Code result = builder.build(codeRewriter, options);
     assert result.isCfCode() || result.isJarCode();
     method.setCode(result);
   }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index ede2e85..3509357 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -129,6 +129,7 @@
     reader.accept(node, ASM6);
     StringWriter writer = new StringWriter();
     for (MethodNode method : node.methods) {
+      writer.append(method.name).append(method.desc).append('\n');
       TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
       method.accept(visitor);
       visitor.p.print(new PrintWriter(writer));
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index d353d88..0fc2489 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -122,6 +122,7 @@
     };
 
     String[] javaBytecodeTests = {
+        "constants.Constants",
         "hello.Hello",
         "arithmetic.Arithmetic",
     };