Allow safe inlining in the main scope and fix a subtle bug.

JsInliner would not hoist a variable if it only appeared in a
var definition, i.e.

  f() { var a = g(); }
  caller() { f(); };

would was inlined as

  caller() { a = g(); }

Also, this patch removes a hack that allowed inlining only in the scope
of certain function calls. It nows allows inlining in the global scope
as long as no variables are extruded.

Change-Id: I44fd3101bd3c9b420bb12b0a2630ee8068e08054
diff --git a/dev/core/src/com/google/gwt/dev/js/JsInliner.java b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
index 11483a5..181d331 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsInliner.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
@@ -62,7 +62,6 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 import com.google.gwt.thirdparty.guava.common.collect.HashMultiset;
-import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
 import com.google.gwt.thirdparty.guava.common.collect.Multiset;
@@ -641,20 +640,9 @@
      */
     private JsFunction programFunction;
 
-    private final Set<JsName> safeToInlineAtTopLevel;
-
     public InliningVisitor(JsProgram program, Set<JsNode> whitelist) {
       this.whitelist = whitelist;
       invocationCountingVisitor.accept(program);
-      JsName defineClass = getFunctionName(program, "JavaClassHierarchySetupUtil.defineClass");
-      // JsInlinerTest doesn't have these functions, but doesn't need them
-      safeToInlineAtTopLevel = defineClass != null ? ImmutableSet.of(defineClass)
-          : ImmutableSet.<JsName>of();
-    }
-
-    private static JsName getFunctionName(JsProgram program, String name) {
-      JsFunction func = program.getIndexedFunction(name);
-      return func != null ? func.getName() : null;
     }
 
     /**
@@ -720,7 +708,6 @@
         } else {
           ctx.replaceMe(new JsEmpty(x.getSourceInfo()));
         }
-
       } else if (x.getExpression() != statements.get(0).getExpression()) {
         // Something has changed
 
@@ -735,7 +722,6 @@
           b.getStatements().addAll(statements);
           ctx.replaceMe(b);
           return;
-
         } else {
           // Insert the new statements into the original context
           for (JsStatement s : statements) {
@@ -839,18 +825,6 @@
     }
 
     @Override
-    public boolean visit(JsExprStmt x, JsContext ctx) {
-      if (functionStack.peek() == programFunction) {
-        /* Don't inline most top-level invocations. */
-        if (x.getExpression() instanceof JsInvocation) {
-          return safeToInlineAtTopLevel.contains(
-              JsUtils.maybeGetFunctionName(x.getExpression()));
-        }
-      }
-      return true;
-    }
-
-    @Override
     public boolean visit(JsFunction x, JsContext ctx) {
       functionStack.push(x);
       newLocalVariableStack.push(Lists.<JsName>newArrayList());
@@ -962,7 +936,7 @@
          * Visit the statement to find names that will be moved to the caller's
          * scope from the invoked function.
          */
-        hoistedNameVisitor.accept(statement);
+        hoistedNameVisitor.accept(h);
 
         if (isReturnStatement(statement)) {
           sawReturnStatement = true;
@@ -977,6 +951,11 @@
        */
       List<JsName> hoistedNames = hoistedNameVisitor.getHoistedNames();
 
+      if (hoistedNames.size() != 0 && callerFunction == programFunction) {
+        // Don't hoist variables into the global scope.
+        return x;
+      }
+
       /*
        * If the inlined method has no return statement, synthesize an undefined
        * reference. It will be reclaimed if the method call is from a
@@ -1015,7 +994,8 @@
       // Perform the name replacement
       NameRefReplacerVisitor v = new NameRefReplacerVisitor(thisExpr,
           x.getArguments(), invokedFunction.getParameters());
-      for (ListIterator<JsName> nameIterator = hoistedNames.listIterator(); nameIterator.hasNext();) {
+      for (ListIterator<JsName> nameIterator = hoistedNames.listIterator();
+          nameIterator.hasNext();) {
         JsName name = nameIterator.next();
 
         /*
@@ -1276,10 +1256,8 @@
          * always flexible, then it would be necessary to clone the expression.
          */
         return paramsToArgsMap.get(name);
-
       } else if (nameReplacements.containsKey(name)) {
         return nameReplacements.get(name).makeRef(sourceInfo);
-
       } else {
         return null;
       }
@@ -1431,7 +1409,6 @@
       if (name == null) {
         // Ignore anonymous functions
         return;
-
       } else if (nameMap.containsKey(name)) {
         /*
          * We have to add the current function as well as the original
@@ -1568,7 +1545,7 @@
    * is inlined even if it slightly increases code size because by doing so it creates more
    * opportunities for the static evaluator.
    */
-  private static final int INLINING_BIAS =  Integer.parseInt(System.getProperty(
+  private static final int INLINING_BIAS = Integer.parseInt(System.getProperty(
       "gwt.jsinlinerInliningBias", "5"));
 
   /**
@@ -1620,7 +1597,6 @@
     return v.containsNestedFunctions();
   }
 
-
   private static OptimizerStats execImpl(JsProgram program, Collection<JsNode> toInline) {
     OptimizerStats stats = new OptimizerStats(NAME);
 
diff --git a/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java b/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java
index 52fdba3..c9cc1be 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java
@@ -57,6 +57,32 @@
     }
   }
 
+  public void testVarInling() throws Exception {
+    String code = Joiner.on('\n').join(
+        "function a1(arg) {var a = arg.f();}",
+        "function caller_doNotInline() { a1(o); }",
+        "caller_doNotInline();");
+
+    String expected = Joiner.on('\n').join(
+        "function caller_doNotInline(){var a;a=o.f()}",
+        "caller_doNotInline();");
+
+    verifyOptimized(expected, code);
+  }
+
+  public void testInlineIntoGlobalScope() throws Exception {
+    verifyOptimized(
+        "o.f();",
+        "function a1(arg) {return arg; } a1(o).f();");
+    verifyOptimized(
+        "o();",
+        "function a1(arg) {return arg()} a1(o);");
+    verifyOptimized(
+        "f(o);",
+        "function a1(arg) {return arg;} f(a1(o));");
+    verifyNoChange("function a1(arg) {var a;  return a = arg; }; a1(o);");
+  }
+
   public void testInlineArrayLiterals() throws Exception {
     String input = "function a1(arg, x) { arg.x = x; return arg; }"
         + "function b1() { var x=a1([], 10); } b1();";
@@ -155,8 +181,7 @@
         "function uniqueId_forceInline(id) {return jsinterop.closure.getUniqueId(id);}",
         "function b1() { uniqueId_forceInline('a'); uniqueId_forceInline('b');  } b1();");
     expected = Joiner.on('\n').join(
-        "function a(){jsinterop.closure.getUniqueId('a');jsinterop.closure.getUniqueId('b')}",
-        "a();");
+        "jsinterop.closure.getUniqueId('a');", "jsinterop.closure.getUniqueId('b')");
     verifyOptimizedObfuscated(expected, input);
 
     // Test DO_NOT_INLINE
@@ -165,8 +190,7 @@
         "function b1() { uniqueId_doNotInline('a'); uniqueId_doNotInline('b');  } b1();");
     expected = Joiner.on('\n').join(
         "function b(a){return jsinterop.closure.getUniqueId(a)}",
-        "function c(){b('a');b('b')}",
-        "c();");
+        "b('a');b('b')");
     verifyOptimizedObfuscated(expected, input);
   }
 
@@ -200,145 +224,141 @@
    * Test that a global array reference breaks argument ordering.
    */
   public void testOrderingArrayGlobal() throws Exception {
-    StringBuilder code = new StringBuilder();
+    String code = Joiner.on('\n').join(
+        "var array; ",
+        "function clinit() { clinit = null; }",
 
-    code.append("var array; ");
-    code.append("function clinit() { clinit = null; }");
+        // callee references array[0] before evaluating argument
+        "function callee(arg) { return array[0] + arg; }",
 
-    // callee references array[0] before evaluating argument
-    code.append("function callee(arg) { return array[0] + arg; }");
+        // non inlineable caller invokes callee with a multi that runs clinit()
+        "function caller_doNotInline() { callee((clinit(),2)); }",
 
-    // caller invokes callee with a multi that runs clinit()
-    code.append("function caller() { callee((clinit(),2)); }");
+        // bootstrap the program
+        "caller_doNotInline();");
 
-    // bootstrap the program
-    code.append("caller();");
-
-    verifyNoChange(code.toString());
+    verifyNoChange(code);
   }
 
   /**
    * Test that a local reference does not break argument ordering.
    */
   public void testOrderingArrayLocal() throws Exception {
-    StringBuilder code = new StringBuilder();
+    String code = Joiner.on('\n').join(
+        "function clinit() { clinit = null; }",
 
-    code.append("function clinit() { clinit = null; }");
+         // callee references array[0] before evaluating argument
+        "function callee(arg) { var array; return array[0] + arg; }",
 
-    // callee references array[0] before evaluating argument
-    code.append("function callee(arg) { var array; return array[0] + arg; }");
+        // caller invokes callee with a multi that runs clinit()
+        "function caller() { callee((clinit(),2)); }",
 
-    // caller invokes callee with a multi that runs clinit()
-    code.append("function caller() { callee((clinit(),2)); }");
+        // bootstrap the program
+        "caller();");
 
-    // bootstrap the program
-    code.append("caller();");
+    String expected = Joiner.on('\n').join(
+        "function clinit() { clinit = null; }",
+        "function caller() { var array; array[0] + (clinit(), 2); }",
+        "caller();");
 
-    StringBuilder expected = new StringBuilder();
-    expected.append("function clinit() { clinit = null; }");
-    expected.append("function caller() { var array; array[0] + (clinit(), 2); }");
-    expected.append("caller();");
-
-    verifyOptimized(expected.toString(), code.toString());
+    verifyOptimized(expected, code);
   }
 
   /**
    * Test that a field reference breaks argument ordering.
    */
   public void testOrderingField() throws Exception {
-    StringBuilder code = new StringBuilder();
+    String code = Joiner.on('\n').join(
+        "function clinit() {  clinit = null; }",
 
-    code.append("function clinit() {  clinit = null; }");
+        // callee references field.x before evaluating argument
+        "function callee(arg) { var field; return field.x + arg; }",
 
-    // callee references field.x before evaluating argument
-    code.append("function callee(arg) { var field; return field.x + arg; }");
+        // caller invokes callee with a multi that runs clinit()
+        "function caller_doNotInline() { callee((clinit(),2)); }",
 
-    // caller invokes callee with a multi that runs clinit()
-    code.append("function caller() { callee((clinit(),2)); }");
+        // bootstrap the program
+        "caller_doNotInline();");
 
-    // bootstrap the program
-    code.append("caller();");
-
-    verifyNoChange(code.toString());
+    verifyNoChange(code);
   }
 
   /**
    * Test that a global variable breaks argument ordering.
    */
   public void testOrderingGlobal() throws Exception {
-    StringBuilder code = new StringBuilder();
-    // A global variable x
-    code.append("var x;");
+    String code = Joiner.on('\n').join(
+        // A global variable x
+        "var x;",
 
-    // clinit() sets up x
-    code.append("function clinit() { x = 1; clinit = null; }");
+        // clinit() sets up x
+        "function clinit() { x = 1; clinit = null; }",
 
-    // callee references x before evaluating argument
-    code.append("function callee(arg) { alert(x); return arg; }");
+        // callee references x before evaluating argument
+        "function callee(arg) { alert(x); return arg; }",
 
-    // caller invokes callee with a multi that runs clinit()
-    code.append("function caller() { callee((clinit(),2)); }");
+        // caller invokes callee with a multi that runs clinit()
+        "function caller_doNotInline() { callee((clinit(),2)); }",
 
-    // bootstrap the program
-    code.append("caller();");
+        // bootstrap the program
+        "caller_doNotInline();");
 
-    verifyNoChange(code.toString());
+    verifyNoChange(code);
   }
 
   /**
    * Test that a local variable does not break argument ordering.
    */
   public void testOrderingLocal() throws Exception {
-    StringBuilder code = new StringBuilder();
+    String code = Joiner.on('\n').join(
+        "function clinit() { clinit = null; }",
 
-    code.append("function clinit() { clinit = null; }");
+        // callee references y before evaluating argument
+        "function callee(arg) { var y; y=2; return arg; }",
 
-    // callee references y before evaluating argument
-    code.append("function callee(arg) { var y; y=2; return arg; }");
+        // caller invokes callee with a multi that runs clinit()
+        "function caller_doNotInline() { return callee((clinit(),3)); }",
 
-    // caller invokes callee with a multi that runs clinit()
-    code.append("function caller() { return callee((clinit(),3)); }");
+        // bootstrap the program
+        "caller_doNotInline();");
 
-    // bootstrap the program
-    code.append("caller();");
+    String expected = Joiner.on('\n').join(
+        "function clinit() { clinit = null; }",
+        "function caller_doNotInline() {var y; return y=2,clinit(),3;}",
+        "caller_doNotInline();");
 
-    StringBuilder expected = new StringBuilder();
-
-    expected.append("function clinit() { clinit = null; }");
-    expected.append("function caller() {var y; return y=2,clinit(),3;}");
-    expected.append("caller();");
-    verifyOptimized(expected.toString(), code.toString());
+    verifyOptimized(expected, code);
   }
 
   /**
    * Test that a new expression breaks argument ordering.
    */
   public void testOrderingNew() throws Exception {
-    StringBuilder code = new StringBuilder();
-    // A static variable x
-    code.append("var x;");
+    String code = Joiner.on('\n').join(
+        // A static variable x
+        "var x;",
 
-    // foo() uses x
-    code.append("function foo() { alert('x = ' + x); }");
+        // foo() uses x
+        "function foo() { alert('x = ' + x); }",
 
-    // callee does "new foo" before evaluating its argument
-    code.append("function callee(arg) { new foo(); return arg; }");
+        // callee does "new foo" before evaluating its argument
+        "function callee(arg) { new foo(); return arg; }",
 
-    // caller invokes callee with a multi that initializes x
-    code.append("function caller() { callee((x=1,2)); }");
+        // caller invokes callee with a multi that initializes x
+        "function caller_doNotInline() { callee((x=1,2)); }",
 
-    // bootstrap the program
-    code.append("caller();");
+        // bootstrap the program
+        "caller_doNotInline();");
 
-    verifyNoChange(code.toString());
+    verifyNoChange(code);
   }
 
   public void testSelfRecursion() throws Exception {
     String input = "function a1() { return blah && b1() }"
-        + "function b1() { return bar && a1()}" + "function c() { a1() } c()";
+        + "function b1() { return bar && a1()}" + "function c_doNotInline() { a1() } c_doNotInline()";
 
     String expected = "function a1() { return blah && bar && a1() }"
-        + "function c() { a1() } c()";
+        + "function c_doNotInline() { a1() } c_doNotInline()";
 
     verifyOptimized(expected, input);
   }
@@ -348,33 +368,32 @@
    * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=5936
    */
   public void testPreserveNameScopeWithDoubleInliningAndObfuscation() throws Exception {
-    StringBuilder code = new StringBuilder();
+    String code = Joiner.on('\n').join(
+        "function getA(){",
+        "  var s;",
+        "  s = getB();",
+        "  return s;",
+        "}",
 
-    code.append("function getA(){"
-                + "var s;"
-                + "s = getB();"
-                + "return s;"
-                + "}");
+        "function getB(){",
+        "  var t;",
+        "  t = 't';",
+        "  t = t + '';",
+        "  return t;",
+        "  }",
 
-    code.append("function getB(){"
-                + "var t;"
-                + "t = 't';"
-                + "t = t + '';"
-                + "return t;"
-                + "}");
+        "function start(y){",
+        "  getA();",
+        "  if (y != 10) {$wnd.alert('y != 10');}",
+        "  }",
 
-    code.append("function start(y){"
-                + "getA();"
-                + "if (y != 10) {$wnd.alert('y != 10');}"
-                + "}");
+        "var x = 10; start(x);");
 
-    code.append("var x = 10; start(x);");
+    String expected = Joiner.on('\n').join(
+        "function c(a){var b;b='t';if(a!=10){$wnd.alert('y != 10')}}",
+        "var d=10;c(d);");
 
-    StringBuilder expected = new StringBuilder();
-    expected.append("function c(a){var b;b='t';if(a!=10){$wnd.alert('y != 10')}}");
-    expected.append("var d=10;c(d);");
-
-    verifyOptimizedObfuscated(expected.toString(), code.toString());
+    verifyOptimizedObfuscated(expected, code);
   }
 
   private void verifyNoChange(String input) throws Exception {