1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.codegen;
  27 
  28 import java.util.ArrayList;
  29 import java.util.HashSet;
  30 import java.util.List;
  31 import java.util.Set;
  32 import jdk.nashorn.internal.codegen.types.Type;
  33 import jdk.nashorn.internal.ir.BinaryNode;
  34 import jdk.nashorn.internal.ir.Block;
  35 import jdk.nashorn.internal.ir.BlockStatement;
  36 import jdk.nashorn.internal.ir.CaseNode;
  37 import jdk.nashorn.internal.ir.EmptyNode;
  38 import jdk.nashorn.internal.ir.Expression;
  39 import jdk.nashorn.internal.ir.FunctionNode;
  40 import jdk.nashorn.internal.ir.IfNode;
  41 import jdk.nashorn.internal.ir.LiteralNode;
  42 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
  43 import jdk.nashorn.internal.ir.Node;
  44 import jdk.nashorn.internal.ir.Statement;
  45 import jdk.nashorn.internal.ir.SwitchNode;
  46 import jdk.nashorn.internal.ir.TernaryNode;
  47 import jdk.nashorn.internal.ir.UnaryNode;
  48 import jdk.nashorn.internal.ir.VarNode;
  49 import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
  50 import jdk.nashorn.internal.runtime.Context;
  51 import jdk.nashorn.internal.runtime.JSType;
  52 import jdk.nashorn.internal.runtime.ScriptRuntime;
  53 import jdk.nashorn.internal.runtime.logging.DebugLogger;
  54 import jdk.nashorn.internal.runtime.logging.Loggable;
  55 import jdk.nashorn.internal.runtime.logging.Logger;
  56 
  57 /**
  58  * Simple constant folding pass, executed before IR is starting to be lowered.
  59  */
  60 @Logger(name="fold")
  61 final class FoldConstants extends SimpleNodeVisitor implements Loggable {
  62 
  63     private final DebugLogger log;
  64 
  65     FoldConstants(final Compiler compiler) {
  66         this.log = initLogger(compiler.getContext());
  67     }
  68 
  69     @Override
  70     public DebugLogger getLogger() {
  71         return log;
  72     }
  73 
  74     @Override
  75     public DebugLogger initLogger(final Context context) {
  76         return context.getLogger(this.getClass());
  77     }
  78 
  79     @Override
  80     public Node leaveUnaryNode(final UnaryNode unaryNode) {
  81         final LiteralNode<?> literalNode = new UnaryNodeConstantEvaluator(unaryNode).eval();
  82         if (literalNode != null) {
  83             log.info("Unary constant folded ", unaryNode, " to ", literalNode);
  84             return literalNode;
  85         }
  86         return unaryNode;
  87     }
  88 
  89     @Override
  90     public Node leaveBinaryNode(final BinaryNode binaryNode) {
  91         final LiteralNode<?> literalNode = new BinaryNodeConstantEvaluator(binaryNode).eval();
  92         if (literalNode != null) {
  93             log.info("Binary constant folded ", binaryNode, " to ", literalNode);
  94             return literalNode;
  95         }
  96         return binaryNode;
  97     }
  98 
  99     @Override
 100     public Node leaveFunctionNode(final FunctionNode functionNode) {
 101         return functionNode;
 102     }
 103 
 104     @Override
 105     public Node leaveIfNode(final IfNode ifNode) {
 106         final Node test = ifNode.getTest();
 107         if (test instanceof LiteralNode.PrimitiveLiteralNode) {
 108             final boolean isTrue = ((LiteralNode.PrimitiveLiteralNode<?>)test).isTrue();
 109             final Block executed = isTrue ? ifNode.getPass() : ifNode.getFail();
 110             final Block dropped  = isTrue ? ifNode.getFail() : ifNode.getPass();
 111             final List<Statement> statements = new ArrayList<>();
 112 
 113             if (executed != null) {
 114                 statements.addAll(executed.getStatements()); // Get statements form executed branch
 115             }
 116             if (dropped != null) {
 117                 extractVarNodesFromDeadCode(dropped, statements); // Get var-nodes from non-executed branch
 118             }
 119             if (statements.isEmpty()) {
 120                 return new EmptyNode(ifNode);
 121             }
 122             return BlockStatement.createReplacement(ifNode, ifNode.getFinish(), statements);
 123         }
 124         return ifNode;
 125     }
 126 
 127     @Override
 128     public Node leaveTernaryNode(final TernaryNode ternaryNode) {
 129         final Node test = ternaryNode.getTest();
 130         if (test instanceof LiteralNode.PrimitiveLiteralNode) {
 131             return (((LiteralNode.PrimitiveLiteralNode<?>)test).isTrue() ? ternaryNode.getTrueExpression() : ternaryNode.getFalseExpression()).getExpression();
 132         }
 133         return ternaryNode;
 134     }
 135 
 136     @Override
 137     public Node leaveSwitchNode(final SwitchNode switchNode) {
 138         return switchNode.setUniqueInteger(lc, isUniqueIntegerSwitchNode(switchNode));
 139     }
 140 
 141     private static boolean isUniqueIntegerSwitchNode(final SwitchNode switchNode) {
 142         final Set<Integer> alreadySeen = new HashSet<>();
 143         for (final CaseNode caseNode : switchNode.getCases()) {
 144             final Expression test = caseNode.getTest();
 145             if (test != null && !isUniqueIntegerLiteral(test, alreadySeen)) {
 146                 return false;
 147             }
 148         }
 149         return true;
 150     }
 151 
 152     private static boolean isUniqueIntegerLiteral(final Expression expr, final Set<Integer> alreadySeen) {
 153         if (expr instanceof LiteralNode) {
 154             final Object value = ((LiteralNode<?>)expr).getValue();
 155             if (value instanceof Integer) {
 156                 return alreadySeen.add((Integer)value);
 157             }
 158         }
 159         return false;
 160     }
 161 
 162     /**
 163      * Helper class to evaluate constant expressions at compile time This is
 164      * also a simplifier used by BinaryNode visits, UnaryNode visits and
 165      * conversions.
 166      */
 167     abstract static class ConstantEvaluator<T extends Node> {
 168         protected T            parent;
 169         protected final long   token;
 170         protected final int    finish;
 171 
 172         protected ConstantEvaluator(final T parent) {
 173             this.parent = parent;
 174             this.token  = parent.getToken();
 175             this.finish = parent.getFinish();
 176         }
 177 
 178         /**
 179          * Returns a literal node that replaces the given parent node, or null if replacement
 180          * is impossible
 181          * @return the literal node
 182          */
 183         protected abstract LiteralNode<?> eval();
 184     }
 185 
 186     /**
 187      * When we eliminate dead code, we must preserve var declarations as they are scoped to the whole
 188      * function. This method gathers var nodes from code passed to it, removing their initializers.
 189      *
 190      * @param deadCodeRoot the root node of eliminated dead code
 191      * @param statements a list that will be receiving the var nodes from the dead code, with their
 192      * initializers removed.
 193      */
 194     static void extractVarNodesFromDeadCode(final Node deadCodeRoot, final List<Statement> statements) {
 195         deadCodeRoot.accept(new SimpleNodeVisitor() {
 196             @Override
 197             public boolean enterVarNode(final VarNode varNode) {
 198                 statements.add(varNode.setInit(null));
 199                 return false;
 200             }
 201 
 202             @Override
 203             public boolean enterFunctionNode(final FunctionNode functionNode) {
 204                 // Don't descend into nested functions
 205                 return false;
 206             }
 207         });
 208     }
 209 
 210     private static class UnaryNodeConstantEvaluator extends ConstantEvaluator<UnaryNode> {
 211         UnaryNodeConstantEvaluator(final UnaryNode parent) {
 212             super(parent);
 213         }
 214 
 215         @Override
 216         protected LiteralNode<?> eval() {
 217             final Node rhsNode = parent.getExpression();
 218 
 219             if (!(rhsNode instanceof LiteralNode)) {
 220                 return null;
 221             }
 222 
 223             if (rhsNode instanceof ArrayLiteralNode) {
 224                 return null;
 225             }
 226 
 227             final LiteralNode<?> rhs = (LiteralNode<?>)rhsNode;
 228             final Type rhsType = rhs.getType();
 229             final boolean rhsInteger = rhsType.isInteger() || rhsType.isBoolean();
 230 
 231             LiteralNode<?> literalNode;
 232 
 233             switch (parent.tokenType()) {
 234             case ADD:
 235                 if (rhsInteger) {
 236                     literalNode = LiteralNode.newInstance(token, finish, rhs.getInt32());
 237                 } else if (rhsType.isLong()) {
 238                     literalNode = LiteralNode.newInstance(token, finish, rhs.getLong());
 239                 } else {
 240                     literalNode = LiteralNode.newInstance(token, finish, rhs.getNumber());
 241                 }
 242                 break;
 243             case SUB:
 244                 if (rhsInteger && rhs.getInt32() != 0) { // @see test/script/basic/minuszero.js
 245                     literalNode = LiteralNode.newInstance(token, finish, -rhs.getInt32());
 246                 } else if (rhsType.isLong() && rhs.getLong() != 0L) {
 247                     literalNode = LiteralNode.newInstance(token, finish, -rhs.getLong());
 248                 } else {
 249                     literalNode = LiteralNode.newInstance(token, finish, -rhs.getNumber());
 250                 }
 251                 break;
 252             case NOT:
 253                 literalNode = LiteralNode.newInstance(token, finish, !rhs.getBoolean());
 254                 break;
 255             case BIT_NOT:
 256                 literalNode = LiteralNode.newInstance(token, finish, ~rhs.getInt32());
 257                 break;
 258             default:
 259                 return null;
 260             }
 261 
 262             return literalNode;
 263         }
 264     }
 265 
 266     //TODO add AND and OR with one constant parameter (bitwise)
 267     private static class BinaryNodeConstantEvaluator extends ConstantEvaluator<BinaryNode> {
 268         BinaryNodeConstantEvaluator(final BinaryNode parent) {
 269             super(parent);
 270         }
 271 
 272         @Override
 273         protected LiteralNode<?> eval() {
 274             LiteralNode<?> result;
 275 
 276             result = reduceTwoLiterals();
 277             if (result != null) {
 278                 return result;
 279             }
 280 
 281             result = reduceOneLiteral();
 282             if (result != null) {
 283                 return result;
 284             }
 285 
 286             return null;
 287         }
 288 
 289         @SuppressWarnings("static-method")
 290         private LiteralNode<?> reduceOneLiteral() {
 291             //TODO handle patterns like AND, OR, numeric ops that can be strength reduced but not replaced by a single literal node etc
 292             return null;
 293         }
 294 
 295         private LiteralNode<?> reduceTwoLiterals() {
 296             if (!(parent.lhs() instanceof LiteralNode && parent.rhs() instanceof LiteralNode)) {
 297                 return null;
 298             }
 299 
 300             final LiteralNode<?> lhs = (LiteralNode<?>)parent.lhs();
 301             final LiteralNode<?> rhs = (LiteralNode<?>)parent.rhs();
 302 
 303             if (lhs instanceof ArrayLiteralNode || rhs instanceof ArrayLiteralNode) {
 304                 return null;
 305             }
 306 
 307             final Type widest = Type.widest(lhs.getType(), rhs.getType());
 308 
 309             boolean isInteger = widest.isInteger();
 310             final double value;
 311 
 312             switch (parent.tokenType()) {
 313             case DIV:
 314                 value = lhs.getNumber() / rhs.getNumber();
 315                 break;
 316             case ADD:
 317                 if ((lhs.isString() || rhs.isNumeric()) && (rhs.isString() || rhs.isNumeric())) {
 318                     final Object res = ScriptRuntime.ADD(lhs.getObject(), rhs.getObject());
 319                     if (res instanceof Number) {
 320                         value = ((Number)res).doubleValue();
 321                         break;
 322                     }
 323                     assert res instanceof CharSequence : res + " was not a CharSequence, it was a " + res.getClass();
 324                     return LiteralNode.newInstance(token, finish, res.toString());
 325                 }
 326                 return null;
 327             case MUL:
 328                 value = lhs.getNumber() * rhs.getNumber();
 329                 break;
 330             case MOD:
 331                 value = lhs.getNumber() % rhs.getNumber();
 332                 break;
 333             case SUB:
 334                 value = lhs.getNumber() - rhs.getNumber();
 335                 break;
 336             case SHR:
 337                 final long result = JSType.toUint32(lhs.getInt32() >>> rhs.getInt32());
 338                 return LiteralNode.newInstance(token, finish, JSType.isRepresentableAsInt(result) ? (int) result : (double) result);
 339             case SAR:
 340                 return LiteralNode.newInstance(token, finish, lhs.getInt32() >> rhs.getInt32());
 341             case SHL:
 342                 return LiteralNode.newInstance(token, finish, lhs.getInt32() << rhs.getInt32());
 343             case BIT_XOR:
 344                 return LiteralNode.newInstance(token, finish, lhs.getInt32() ^ rhs.getInt32());
 345             case BIT_AND:
 346                 return LiteralNode.newInstance(token, finish, lhs.getInt32() & rhs.getInt32());
 347             case BIT_OR:
 348                 return LiteralNode.newInstance(token, finish, lhs.getInt32() | rhs.getInt32());
 349             case GE:
 350                 return LiteralNode.newInstance(token, finish, ScriptRuntime.GE(lhs.getObject(), rhs.getObject()));
 351             case LE:
 352                 return LiteralNode.newInstance(token, finish, ScriptRuntime.LE(lhs.getObject(), rhs.getObject()));
 353             case GT:
 354                 return LiteralNode.newInstance(token, finish, ScriptRuntime.GT(lhs.getObject(), rhs.getObject()));
 355             case LT:
 356                 return LiteralNode.newInstance(token, finish, ScriptRuntime.LT(lhs.getObject(), rhs.getObject()));
 357             case NE:
 358                 return LiteralNode.newInstance(token, finish, ScriptRuntime.NE(lhs.getObject(), rhs.getObject()));
 359             case NE_STRICT:
 360                 return LiteralNode.newInstance(token, finish, ScriptRuntime.NE_STRICT(lhs.getObject(), rhs.getObject()));
 361             case EQ:
 362                 return LiteralNode.newInstance(token, finish, ScriptRuntime.EQ(lhs.getObject(), rhs.getObject()));
 363             case EQ_STRICT:
 364                 return LiteralNode.newInstance(token, finish, ScriptRuntime.EQ_STRICT(lhs.getObject(), rhs.getObject()));
 365             default:
 366                 return null;
 367             }
 368 
 369             isInteger &= JSType.isStrictlyRepresentableAsInt(value);
 370 
 371             if (isInteger) {
 372                 return LiteralNode.newInstance(token, finish, (int)value);
 373             }
 374 
 375             return LiteralNode.newInstance(token, finish, value);
 376         }
 377     }
 378 }