Strenghten DeadCodeElimination.

Use nullness information computed by TypeTightner to simplify
expressions of the form

    a == null

when a is determined to be non null.

Change-Id: Ief863dc3e2d41334c1a9521ed2a3ca526c2d2f88
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JPrimitiveType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JPrimitiveType.java
index 696ddf7..45ac24a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JPrimitiveType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JPrimitiveType.java
@@ -75,6 +75,16 @@
   }
 
   /**
+   * Returns <code>true</code> if it's possible for this type to be
+   * <code>null</code>.
+   *
+   * @see JNonNullType
+   */
+  public boolean canBeNull() {
+    return false;
+  }
+
+  /**
    * Returns a literal which has been coerced to this type, or <code>null</code>
    * if no such coercion is possible.
    */
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JType.java
index 805759f..c0f3851 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JType.java
@@ -54,6 +54,14 @@
     this.name = StringInterner.get().intern(name);
   }
 
+  /**
+   * Returns <code>true</code> if it's possible for this type to be
+   * <code>null</code>.
+   *
+   * @see JNonNullType
+   */
+  public abstract boolean canBeNull();
+
   public abstract JLiteral getDefaultValue();
 
   public abstract String getJavahSignatureName();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index bbe809a..381f5d3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -67,6 +67,9 @@
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
 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.base.Predicate;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -155,21 +158,9 @@
           simplifyXor(lhs, rhs, ctx);
           break;
         case EQ:
-          // simplify: null == null -> true
-          if (lhs.getType() == program.getTypeNull() && rhs.getType() == program.getTypeNull()
-              && !x.hasSideEffects()) {
-            ctx.replaceMe(program.getLiteralBoolean(true));
-            return;
-          }
           simplifyEq(lhs, rhs, ctx, false);
           break;
         case NEQ:
-          // simplify: null != null -> false
-          if (lhs.getType() == program.getTypeNull() && rhs.getType() == program.getTypeNull()
-              && !x.hasSideEffects()) {
-            ctx.replaceMe(program.getLiteralBoolean(false));
-            return;
-          }
           simplifyEq(lhs, rhs, ctx, true);
           break;
         case ADD:
@@ -1493,13 +1484,52 @@
       return false;
     }
 
+    private AnalysisResult staticallyEvaluateEq(JExpression lhs, JExpression rhs) {
+      if (lhs.getType() == program.getTypeNull() && rhs.getType() == program.getTypeNull()) {
+        return AnalysisResult.TRUE;
+      }
+      if (lhs.getType() == program.getTypeNull() && !rhs.getType().canBeNull() ||
+          rhs.getType() == program.getTypeNull() && !lhs.getType().canBeNull()) {
+        return AnalysisResult.FALSE;
+      }
+      return AnalysisResult.UNKNOWN;
+    }
+
+    private JExpression simplifyNonSideEffects(JExpression result,
+        JExpression... evaluateIfSideEffects) {
+
+      List<JExpression> expressionsWithSideEffects = Lists.newArrayList(Iterables.filter(
+          Arrays.asList(evaluateIfSideEffects), new Predicate<JExpression>() {
+        @Override
+        public boolean apply(JExpression expression) {
+          return expression.hasSideEffects();
+        }
+      }));
+
+      if (expressionsWithSideEffects.isEmpty()) {
+        return result;
+      }
+
+      expressionsWithSideEffects.add(result);
+      return new JMultiExpression(expressionsWithSideEffects.get(0).getSourceInfo(),
+          expressionsWithSideEffects);
+    }
+
     /**
      * Simplify <code>lhs == rhs</code>. If <code>negate</code> is true, then
      * it's actually static evaluation of <code>lhs != rhs</code>.
      */
-    private void simplifyEq(JExpression lhs, JExpression rhs, Context ctx, boolean negated) {
+    private void simplifyEq(JExpression lhs, JExpression rhs, Context ctx, boolean negate) {
+      // simplify: null == null -> true
+      AnalysisResult analysisResult = staticallyEvaluateEq(lhs, rhs);
+      if (analysisResult != AnalysisResult.UNKNOWN) {
+        ctx.replaceMe(simplifyNonSideEffects(
+            program.getLiteralBoolean(negate ^ (analysisResult == AnalysisResult.TRUE)), lhs, rhs));
+        return;
+      }
+
       if (isTypeBoolean(lhs) && isTypeBoolean(rhs)) {
-        simplifyBooleanEq(lhs, rhs, ctx, negated);
+        simplifyBooleanEq(lhs, rhs, ctx, negate);
         return;
       }
     }
@@ -1917,4 +1947,6 @@
     optimizeEvent.end("didChange", "" + stats.didChange());
     return stats;
   }
+
+  private enum AnalysisResult { TRUE, FALSE, UNKNOWN };
 }
diff --git a/user/test/com/google/gwt/dev/jjs/test/FieldInitOrderChild.java b/user/test/com/google/gwt/dev/jjs/test/FieldInitOrderChild.java
index 78b6fb3..56df745 100644
--- a/user/test/com/google/gwt/dev/jjs/test/FieldInitOrderChild.java
+++ b/user/test/com/google/gwt/dev/jjs/test/FieldInitOrderChild.java
@@ -56,8 +56,19 @@
     // i3, i4 and i7 would be directly converted into strings hence show undefined instead of null.
     // String operations should take care of corner cases where a string is null or undefined
     // see issue 8257.
-    seenValues.add("i1=" + i1 + ",i2=" + i2 + ",i3=" + (i3 == null ? "null" : i3) +
-        ",i4=" +  (i4 == null ? "null" : i4) + ",i5=" + i5 + ",i6=" + i6
-        + ",i7=" + (i7 == null ? "null" : i7));
+    seenValues.add("i1=" + i1 + ",i2=" + i2 + ",i3=" + undefinedToNull(i3) +
+        ",i4=" +  undefinedToNull(i4) + ",i5=" + i5 + ",i6=" + i6
+        + ",i7=" + undefinedToNull(i7));
+  }
+
+  // Convert undefined to null this way {@code undefinedToNull(i)} instead of
+  // {@code i == null ? "null" : i} to avoid the shortcomings of our nullness analysis in
+  // {@link TypeTightener}, which will infer that i3 is never null.
+  private String undefinedToNull(Object o) {
+    String s = "" + o;
+    if (s.equals("undefined")) {
+      return "null";
+    }
+    return s;
   }
 }
\ No newline at end of file