Add --target-api=N option to dx.

This change adds the option and plumbs it into where it
needs to go, but doesn't add any code to take action on it.
That will come in a follow-up.

Bug: 4094709
Change-Id: I9c796e176e125b0bcee18af56d9e6da802dfa081
diff --git a/dx/src/com/android/dx/command/dexer/Main.java b/dx/src/com/android/dx/command/dexer/Main.java
index 6e8fa7e..a2133d5 100644
--- a/dx/src/com/android/dx/command/dexer/Main.java
+++ b/dx/src/com/android/dx/command/dexer/Main.java
@@ -23,6 +23,7 @@
 import com.android.dx.command.DxConsole;
 import com.android.dx.command.UsageException;
 import com.android.dx.dex.DexFormat;
+import com.android.dx.dex.DexOptions;
 import com.android.dx.dex.cf.CfOptions;
 import com.android.dx.dex.cf.CfTranslator;
 import com.android.dx.dex.cf.CodeStatistics;
@@ -464,7 +465,7 @@
 
         try {
             ClassDefItem clazz =
-                CfTranslator.translate(name, bytes, args.cfOptions);
+                CfTranslator.translate(name, bytes, args.cfOptions, args.dexOptions);
             synchronized (outputDex) {
                 outputDex.add(clazz);
             }
@@ -901,6 +902,9 @@
          */
         public boolean keepClassesInJar = false;
 
+        /** what API level to target */
+        public int targetApiLevel = Integer.MAX_VALUE;
+
         /** how much source position info to preserve */
         public int positionInfo = PositionList.LINES;
 
@@ -910,7 +914,7 @@
         /** whether to merge with the output dex file if it exists. */
         public boolean incremental = false;
 
-        /** {@code non-null after {@link #parse};} file name arguments */
+        /** {@code non-null} after {@link #parse}; file name arguments */
         public String[] fileNames;
 
         /** whether to do SSA/register optimization */
@@ -925,9 +929,12 @@
         /** Whether to print statistics to stdout at end of compile cycle */
         public boolean statistics;
 
-        /** Options for dex.cf.* */
+        /** Options for class file transformation */
         public CfOptions cfOptions;
 
+        /** Options for dex file output */
+        public DexOptions dexOptions;
+
         /** number of threads to run with */
         public int numThreads = 1;
 
@@ -997,6 +1004,19 @@
                 } else if (arg.startsWith("--dump-method=")) {
                     methodToDump = arg.substring(arg.indexOf('=') + 1);
                     jarOutput = false;
+                } else if (arg.startsWith("--target-api=")) {
+                    arg = arg.substring(arg.indexOf('=') + 1);
+                    int value;
+                    try {
+                        value = Integer.parseInt(arg);
+                    } catch (NumberFormatException ex) {
+                        value = -1;
+                    }
+                    if (value < 1) {
+                        System.err.println("improper target-api option: " + arg);
+                        throw new UsageException();
+                    }
+                    targetApiLevel = value;
                 } else if (arg.startsWith("--positions=")) {
                     String pstr = arg.substring(arg.indexOf('=') + 1).intern();
                     if (pstr == "none") {
@@ -1044,6 +1064,7 @@
             }
 
             makeCfOptions();
+            makeDexOptions();
         }
 
         /**
@@ -1061,6 +1082,15 @@
             cfOptions.statistics = statistics;
             cfOptions.warn = DxConsole.err;
         }
+
+        /**
+         * Copies relevent arguments over into a DexOptions instance.
+         */
+        private void makeDexOptions() {
+            dexOptions = new DexOptions();
+
+            dexOptions.enableExtendedOpcodes = targetApiLevel >= 12;
+        }
     }
 
     /** Runnable helper class to process files in multiple threads */
diff --git a/dx/src/com/android/dx/dex/DexOptions.java b/dx/src/com/android/dx/dex/DexOptions.java
new file mode 100644
index 0000000..bd07d6d
--- /dev/null
+++ b/dx/src/com/android/dx/dex/DexOptions.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.dex;
+
+/**
+ * Container for options used to control details of dex file generation.
+ */
+public class DexOptions {
+    /** whether extended opcodes are allowed */
+    public boolean enableExtendedOpcodes = true;
+}
diff --git a/dx/src/com/android/dx/dex/cf/CfTranslator.java b/dx/src/com/android/dx/dex/cf/CfTranslator.java
index 1a9aa47..8bcfa3d 100644
--- a/dx/src/com/android/dx/dex/cf/CfTranslator.java
+++ b/dx/src/com/android/dx/dex/cf/CfTranslator.java
@@ -24,6 +24,7 @@
 import com.android.dx.cf.iface.FieldList;
 import com.android.dx.cf.iface.Method;
 import com.android.dx.cf.iface.MethodList;
+import com.android.dx.dex.DexOptions;
 import com.android.dx.dex.code.DalvCode;
 import com.android.dx.dex.code.PositionList;
 import com.android.dx.dex.code.RopTranslator;
@@ -76,13 +77,14 @@
      * @param filePath {@code non-null;} the file path for the class,
      * excluding any base directory specification
      * @param bytes {@code non-null;} contents of the file
-     * @param args command-line arguments
+     * @param cfOptions options for class translation
+     * @param dexOptions options for dex output
      * @return {@code non-null;} the translated class
      */
     public static ClassDefItem translate(String filePath, byte[] bytes,
-            CfOptions args) {
+            CfOptions cfOptions, DexOptions dexOptions) {
         try {
-            return translate0(filePath, bytes, args);
+            return translate0(filePath, bytes, cfOptions, dexOptions);
         } catch (RuntimeException ex) {
             String msg = "...while processing " + filePath;
             throw ExceptionWithContext.withContext(ex, msg);
@@ -97,38 +99,39 @@
      * @param filePath {@code non-null;} the file path for the class,
      * excluding any base directory specification
      * @param bytes {@code non-null;} contents of the file
-     * @param args command-line arguments
+     * @param cfOptions options for class translation
+     * @param dexOptions options for dex output
      * @return {@code non-null;} the translated class
      */
     private static ClassDefItem translate0(String filePath, byte[] bytes,
-            CfOptions args) {
+            CfOptions cfOptions, DexOptions dexOptions) {
         DirectClassFile cf =
-            new DirectClassFile(bytes, filePath, args.strictNameCheck);
+            new DirectClassFile(bytes, filePath, cfOptions.strictNameCheck);
 
         cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
         cf.getMagic();
 
-        OptimizerOptions.loadOptimizeLists(args.optimizeListFile,
-                args.dontOptimizeListFile);
+        OptimizerOptions.loadOptimizeLists(cfOptions.optimizeListFile,
+                cfOptions.dontOptimizeListFile);
 
         // Build up a class to output.
 
         CstType thisClass = cf.getThisClass();
         int classAccessFlags = cf.getAccessFlags() & ~AccessFlags.ACC_SUPER;
-        CstUtf8 sourceFile = (args.positionInfo == PositionList.NONE) ? null :
+        CstUtf8 sourceFile = (cfOptions.positionInfo == PositionList.NONE) ? null :
             cf.getSourceFile();
         ClassDefItem out =
             new ClassDefItem(thisClass, classAccessFlags,
                     cf.getSuperclass(), cf.getInterfaces(), sourceFile);
 
         Annotations classAnnotations =
-            AttributeTranslator.getClassAnnotations(cf, args);
+            AttributeTranslator.getClassAnnotations(cf, cfOptions);
         if (classAnnotations.size() != 0) {
             out.setClassAnnotations(classAnnotations);
         }
 
         processFields(cf, out);
-        processMethods(cf, args, out);
+        processMethods(cf, cfOptions, dexOptions, out);
 
         return out;
     }
@@ -214,11 +217,12 @@
      * Processes the methods of the given class.
      *
      * @param cf {@code non-null;} class being translated
-     * @param args {@code non-null;} command-line args
+     * @param cfOptions {@code non-null;} options for class translation
+     * @param dexOptions {@code non-null;} options for dex output
      * @param out {@code non-null;} output class
      */
-    private static void processMethods(DirectClassFile cf,
-            CfOptions args, ClassDefItem out) {
+    private static void processMethods(DirectClassFile cf, CfOptions cfOptions,
+            DexOptions dexOptions, ClassDefItem out) {
         CstType thisClass = cf.getThisClass();
         MethodList methods = cf.getMethods();
         int sz = methods.size();
@@ -242,8 +246,8 @@
                 } else {
                     ConcreteMethod concrete =
                         new ConcreteMethod(one, cf,
-                                (args.positionInfo != PositionList.NONE),
-                                args.localInfo);
+                                (cfOptions.positionInfo != PositionList.NONE),
+                                cfOptions.localInfo);
 
                     TranslationAdvice advice;
 
@@ -259,7 +263,7 @@
                             = thisClass.getClassType().getDescriptor()
                                 + "." + one.getName().getString();
 
-                    if (args.optimize &&
+                    if (cfOptions.optimize &&
                             OptimizerOptions.shouldOptimize(canonicalName)) {
                         if (DEBUG) {
                             System.err.println("Optimizing " + canonicalName);
@@ -267,14 +271,14 @@
 
                         nonOptRmeth = rmeth;
                         rmeth = Optimizer.optimize(rmeth,
-                                paramSize, isStatic, args.localInfo, advice);
+                                paramSize, isStatic, cfOptions.localInfo, advice);
 
                         if (DEBUG) {
                             OptimizerOptions.compareOptimizerStep(nonOptRmeth,
-                                    paramSize, isStatic, args, advice, rmeth);
+                                    paramSize, isStatic, cfOptions, advice, rmeth);
                         }
 
-                        if (args.statistics) {
+                        if (cfOptions.statistics) {
                             CodeStatistics.updateRopStatistics(
                                     nonOptRmeth, rmeth);
                         }
@@ -282,15 +286,15 @@
 
                     LocalVariableInfo locals = null;
 
-                    if (args.localInfo) {
+                    if (cfOptions.localInfo) {
                         locals = LocalVariableExtractor.extract(rmeth);
                     }
 
-                    code = RopTranslator.translate(rmeth, args.positionInfo,
-                            locals, paramSize);
+                    code = RopTranslator.translate(rmeth, cfOptions.positionInfo,
+                            locals, paramSize, dexOptions);
 
-                    if (args.statistics && nonOptRmeth != null) {
-                        updateDexStatistics(args, rmeth, nonOptRmeth, locals,
+                    if (cfOptions.statistics && nonOptRmeth != null) {
+                        updateDexStatistics(cfOptions, dexOptions, rmeth, nonOptRmeth, locals,
                                 paramSize, concrete.getCode().size());
                     }
                 }
@@ -345,7 +349,7 @@
     /**
      * Helper that updates the dex statistics.
      */
-    private static void updateDexStatistics(CfOptions args,
+    private static void updateDexStatistics(CfOptions cfOptions, DexOptions dexOptions,
             RopMethod optRmeth, RopMethod nonOptRmeth,
             LocalVariableInfo locals, int paramSize, int originalByteCount) {
         /*
@@ -357,9 +361,9 @@
          */
 
         DalvCode optCode = RopTranslator.translate(optRmeth,
-                args.positionInfo, locals, paramSize);
+                cfOptions.positionInfo, locals, paramSize, dexOptions);
         DalvCode nonOptCode = RopTranslator.translate(nonOptRmeth,
-                args.positionInfo, locals, paramSize);
+                cfOptions.positionInfo, locals, paramSize, dexOptions);
 
         /*
          * Fake out the indices, so code.getInsns() can work well enough
diff --git a/dx/src/com/android/dx/dex/code/Dops.java b/dx/src/com/android/dx/dex/code/Dops.java
index 667b326..df88186 100644
--- a/dx/src/com/android/dx/dex/code/Dops.java
+++ b/dx/src/com/android/dx/dex/code/Dops.java
@@ -16,6 +16,7 @@
 
 package com.android.dx.dex.code;
 
+import com.android.dx.dex.DexOptions;
 import com.android.dx.dex.code.form.Form10t;
 import com.android.dx.dex.code.form.Form10x;
 import com.android.dx.dex.code.form.Form11n;
@@ -1396,11 +1397,13 @@
      * given instance, if any.
      *
      * @param opcode {@code non-null;} the opcode
+     * @param options {@code non-null;} options, used to determine
+     * which opcodes are potentially off-limits
      * @return {@code null-ok;} the next opcode in the same family, in the
      * chain of opcodes to try, or {@code null} if the given opcode is
      * the last in its chain
      */
-    public static Dop getNextOrNull(Dop opcode) {
+    public static Dop getNextOrNull(Dop opcode, DexOptions options) {
         int nextOpcode = opcode.getNextOpcode();
 
         if (nextOpcode == Opcodes.NO_NEXT) {
diff --git a/dx/src/com/android/dx/dex/code/OutputCollector.java b/dx/src/com/android/dx/dex/code/OutputCollector.java
index d78e5fc..a5e54a8 100644
--- a/dx/src/com/android/dx/dex/code/OutputCollector.java
+++ b/dx/src/com/android/dx/dex/code/OutputCollector.java
@@ -16,6 +16,8 @@
 
 package com.android.dx.dex.code;
 
+import com.android.dx.dex.DexOptions;
+
 import java.util.ArrayList;
 
 /**
@@ -42,14 +44,15 @@
     /**
      * Constructs an instance.
      *
+     * @param dexOptions {@code non-null;} options for dex output
      * @param initialCapacity {@code >= 0;} initial capacity of the output list
      * @param suffixInitialCapacity {@code >= 0;} initial capacity of the output
      * suffix
      * @param regCount {@code >= 0;} register count for the method
      */
-    public OutputCollector(int initialCapacity, int suffixInitialCapacity,
+    public OutputCollector(DexOptions dexOptions, int initialCapacity, int suffixInitialCapacity,
             int regCount) {
-        this.finisher = new OutputFinisher(initialCapacity, regCount);
+        this.finisher = new OutputFinisher(dexOptions, initialCapacity, regCount);
         this.suffix = new ArrayList<DalvInsn>(suffixInitialCapacity);
     }
 
diff --git a/dx/src/com/android/dx/dex/code/OutputFinisher.java b/dx/src/com/android/dx/dex/code/OutputFinisher.java
index 118d184..1b13fab 100644
--- a/dx/src/com/android/dx/dex/code/OutputFinisher.java
+++ b/dx/src/com/android/dx/dex/code/OutputFinisher.java
@@ -16,6 +16,7 @@
 
 package com.android.dx.dex.code;
 
+import com.android.dx.dex.DexOptions;
 import com.android.dx.io.Opcodes;
 import com.android.dx.rop.code.LocalItem;
 import com.android.dx.rop.code.RegisterSpec;
@@ -38,6 +39,9 @@
  * form of a {@link DalvInsnList} instance.
  */
 public final class OutputFinisher {
+    /** {@code non-null;} options for dex output */
+    private final DexOptions dexOptions;
+
     /**
      * {@code >= 0;} register count for the method, not including any extra
      * "reserved" registers needed to translate "difficult" instructions
@@ -64,11 +68,13 @@
     /**
      * Constructs an instance. It initially contains no instructions.
      *
+     * @param dexOptions {@code non-null;} options for dex output
      * @param regCount {@code >= 0;} register count for the method
      * @param initialCapacity {@code >= 0;} initial capacity of the
      * instructions list
      */
-    public OutputFinisher(int initialCapacity, int regCount) {
+    public OutputFinisher(DexOptions dexOptions, int initialCapacity, int regCount) {
+        this.dexOptions = dexOptions;
         this.unreservedRegCount = regCount;
         this.insns = new ArrayList<DalvInsn>(initialCapacity);
         this.reservedCount = -1;
@@ -500,7 +506,7 @@
                 break;
             }
 
-            guess = Dops.getNextOrNull(guess);
+            guess = Dops.getNextOrNull(guess, dexOptions);
         }
 
         return guess;
diff --git a/dx/src/com/android/dx/dex/code/RopTranslator.java b/dx/src/com/android/dx/dex/code/RopTranslator.java
index 9899c43..3d24c4f 100644
--- a/dx/src/com/android/dx/dex/code/RopTranslator.java
+++ b/dx/src/com/android/dx/dex/code/RopTranslator.java
@@ -16,6 +16,7 @@
 
 package com.android.dx.dex.code;
 
+import com.android.dx.dex.DexOptions;
 import com.android.dx.io.Opcodes;
 import com.android.dx.rop.code.BasicBlock;
 import com.android.dx.rop.code.BasicBlockList;
@@ -46,6 +47,9 @@
  * #translate} method is the thing to call on this class.
  */
 public final class RopTranslator {
+    /** {@code non-null;} options for dex output */
+    private final DexOptions dexOptions;
+
     /** {@code non-null;} method to translate */
     private final RopMethod method;
 
@@ -92,13 +96,13 @@
      * @param locals {@code null-ok;} local variable information to use
      * @param paramSize size, in register units, of all the parameters to
      * this method
+     * @param dexOptions {@code non-null;} options for dex output
      * @return {@code non-null;} the translated version
      */
     public static DalvCode translate(RopMethod method, int positionInfo,
-                                     LocalVariableInfo locals, int paramSize) {
+            LocalVariableInfo locals, int paramSize, DexOptions dexOptions) {
         RopTranslator translator =
-            new RopTranslator(method, positionInfo, locals,
-                    paramSize);
+            new RopTranslator(method, positionInfo, locals, paramSize, dexOptions);
         return translator.translateAndGetResult();
     }
 
@@ -111,9 +115,11 @@
      * @param locals {@code null-ok;} local variable information to use
      * @param paramSize size, in register units, of all the parameters to
      * this method
+     * @param dexOptions {@code non-null;} options for dex output
      */
-    private RopTranslator(RopMethod method, int positionInfo,
-                          LocalVariableInfo locals, int paramSize) {
+    private RopTranslator(RopMethod method, int positionInfo, LocalVariableInfo locals,
+            int paramSize, DexOptions dexOptions) {
+        this.dexOptions = dexOptions;
         this.method = method;
         this.positionInfo = positionInfo;
         this.locals = locals;
@@ -150,7 +156,7 @@
         this.regCount = blocks.getRegCount()
                 + (paramsAreInOrder ? 0 : this.paramSize);
 
-        this.output = new OutputCollector(maxInsns, bsz * 3, regCount);
+        this.output = new OutputCollector(dexOptions, maxInsns, bsz * 3, regCount);
 
         if (locals != null) {
             this.translationVisitor =