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 static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX;
  29 
  30 import java.util.ArrayList;
  31 import java.util.HashMap;
  32 import java.util.List;
  33 import java.util.Map;
  34 import jdk.nashorn.internal.ir.Block;
  35 import jdk.nashorn.internal.ir.FunctionNode;
  36 import jdk.nashorn.internal.ir.LiteralNode;
  37 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
  38 import jdk.nashorn.internal.ir.Node;
  39 import jdk.nashorn.internal.ir.ObjectNode;
  40 import jdk.nashorn.internal.ir.PropertyNode;
  41 import jdk.nashorn.internal.ir.SplitNode;
  42 import jdk.nashorn.internal.ir.Splittable;
  43 import jdk.nashorn.internal.ir.Statement;
  44 import jdk.nashorn.internal.ir.VarNode;
  45 import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
  46 import jdk.nashorn.internal.runtime.Context;
  47 import jdk.nashorn.internal.runtime.logging.DebugLogger;
  48 import jdk.nashorn.internal.runtime.logging.Loggable;
  49 import jdk.nashorn.internal.runtime.logging.Logger;
  50 import jdk.nashorn.internal.runtime.options.Options;
  51 
  52 /**
  53  * Split the IR into smaller compile units.
  54  */
  55 @Logger(name="splitter")
  56 final class Splitter extends SimpleNodeVisitor implements Loggable {
  57     /** Current compiler. */
  58     private final Compiler compiler;
  59 
  60     /** IR to be broken down. */
  61     private final FunctionNode outermost;
  62 
  63     /** Compile unit for the main script. */
  64     private final CompileUnit outermostCompileUnit;
  65 
  66     /** Cache for calculated block weights. */
  67     private final Map<Node, Long> weightCache = new HashMap<>();
  68 
  69     /** Weight threshold for when to start a split. */
  70     public static final long SPLIT_THRESHOLD = Options.getIntProperty("nashorn.compiler.splitter.threshold", 32 * 1024);
  71 
  72     private final DebugLogger log;
  73 
  74     /**
  75      * Constructor.
  76      *
  77      * @param compiler              the compiler
  78      * @param functionNode          function node to split
  79      * @param outermostCompileUnit  compile unit for outermost function, if non-lazy this is the script's compile unit
  80      */
  81     public Splitter(final Compiler compiler, final FunctionNode functionNode, final CompileUnit outermostCompileUnit) {
  82         this.compiler             = compiler;
  83         this.outermost            = functionNode;
  84         this.outermostCompileUnit = outermostCompileUnit;
  85         this.log                  = initLogger(compiler.getContext());
  86     }
  87 
  88     @Override
  89     public DebugLogger initLogger(final Context context) {
  90         return context.getLogger(this.getClass());
  91     }
  92 
  93     @Override
  94     public DebugLogger getLogger() {
  95         return log;
  96     }
  97 
  98     /**
  99      * Execute the split.
 100      * @param fn the function to split
 101      * @param top whether this is the topmost compiled function (it's either a program, or we're doing a recompilation).
 102      */
 103     FunctionNode split(final FunctionNode fn, final boolean top) {
 104         FunctionNode functionNode = fn;
 105 
 106         log.fine("Initiating split of '", functionNode.getName(), "'");
 107 
 108         long weight = WeighNodes.weigh(functionNode);
 109 
 110         // We know that our LexicalContext is empty outside the call to functionNode.accept(this) below,
 111         // so we can pass null to all methods expecting a LexicalContext parameter.
 112         assert lc.isEmpty() : "LexicalContext not empty";
 113 
 114         if (weight >= SPLIT_THRESHOLD) {
 115             log.info("Splitting function '", functionNode.getName(), "' as its weight ", weight, " exceeds split threshold ", SPLIT_THRESHOLD);
 116             functionNode = (FunctionNode)functionNode.accept(this);
 117 
 118             if (functionNode.isSplit()) {
 119                 // Weight has changed so weigh again, this time using block weight cache
 120                 weight = WeighNodes.weigh(functionNode, weightCache);
 121                 functionNode = functionNode.setBody(null, functionNode.getBody().setNeedsScope(null));
 122             }
 123 
 124             if (weight >= SPLIT_THRESHOLD) {
 125                 functionNode = functionNode.setBody(null, splitBlock(functionNode.getBody(), functionNode));
 126                 functionNode = functionNode.setFlag(null, FunctionNode.IS_SPLIT);
 127                 weight = WeighNodes.weigh(functionNode.getBody(), weightCache);
 128             }
 129         }
 130 
 131         assert functionNode.getCompileUnit() == null : "compile unit already set for " + functionNode.getName();
 132 
 133         if (top) {
 134             assert outermostCompileUnit != null : "outermost compile unit is null";
 135             functionNode = functionNode.setCompileUnit(null, outermostCompileUnit);
 136             outermostCompileUnit.addWeight(weight + WeighNodes.FUNCTION_WEIGHT);
 137         } else {
 138             functionNode = functionNode.setCompileUnit(null, findUnit(weight));
 139         }
 140 
 141         final Block body = functionNode.getBody();
 142         final List<FunctionNode> dc = directChildren(functionNode);
 143 
 144         final Block newBody = (Block)body.accept(new SimpleNodeVisitor() {
 145             @Override
 146             public boolean enterFunctionNode(final FunctionNode nestedFunction) {
 147                 return dc.contains(nestedFunction);
 148             }
 149 
 150             @Override
 151             public Node leaveFunctionNode(final FunctionNode nestedFunction) {
 152                 final FunctionNode split = new Splitter(compiler, nestedFunction, outermostCompileUnit).split(nestedFunction, false);
 153                 lc.replace(nestedFunction, split);
 154                 return split;
 155             }
 156         });
 157         functionNode = functionNode.setBody(null, newBody);
 158 
 159         assert functionNode.getCompileUnit() != null;
 160 
 161         return functionNode;
 162     }
 163 
 164     private static List<FunctionNode> directChildren(final FunctionNode functionNode) {
 165         final List<FunctionNode> dc = new ArrayList<>();
 166         functionNode.accept(new SimpleNodeVisitor() {
 167             @Override
 168             public boolean enterFunctionNode(final FunctionNode child) {
 169                 if (child == functionNode) {
 170                     return true;
 171                 }
 172                 if (lc.getParentFunction(child) == functionNode) {
 173                     dc.add(child);
 174                 }
 175                 return false;
 176             }
 177         });
 178         return dc;
 179     }
 180 
 181     /**
 182      * Override this logic to look up compile units in a different way
 183      * @param weight weight needed
 184      * @return compile unit
 185      */
 186     protected CompileUnit findUnit(final long weight) {
 187         return compiler.findUnit(weight);
 188     }
 189 
 190     /**
 191      * Split a block into sub methods.
 192      *
 193      * @param block Block or function to split.
 194      *
 195      * @return new weight for the resulting block.
 196      */
 197     private Block splitBlock(final Block block, final FunctionNode function) {
 198 
 199         final List<Statement> splits = new ArrayList<>();
 200         List<Statement> statements = new ArrayList<>();
 201         long statementsWeight = 0;
 202 
 203         for (final Statement statement : block.getStatements()) {
 204             final long weight = WeighNodes.weigh(statement, weightCache);
 205             final boolean isBlockScopedVarNode = isBlockScopedVarNode(statement);
 206 
 207             if (statementsWeight + weight >= SPLIT_THRESHOLD || statement.isTerminal() || isBlockScopedVarNode) {
 208                 if (!statements.isEmpty()) {
 209                     splits.add(createBlockSplitNode(block, function, statements, statementsWeight));
 210                     statements = new ArrayList<>();
 211                     statementsWeight = 0;
 212                 }
 213             }
 214 
 215             if (statement.isTerminal() || isBlockScopedVarNode) {
 216                 splits.add(statement);
 217             } else {
 218                 statements.add(statement);
 219                 statementsWeight += weight;
 220             }
 221         }
 222 
 223         if (!statements.isEmpty()) {
 224             splits.add(createBlockSplitNode(block, function, statements, statementsWeight));
 225         }
 226 
 227         return block.setStatements(lc, splits);
 228     }
 229 
 230     /**
 231      * Create a new split node from statements contained in a parent block.
 232      *
 233      * @param parent     Parent block.
 234      * @param statements Statements to include.
 235      *
 236      * @return New split node.
 237      */
 238     private SplitNode createBlockSplitNode(final Block parent, final FunctionNode function, final List<Statement> statements, final long weight) {
 239         final long   token      = parent.getToken();
 240         final int    finish     = parent.getFinish();
 241         final String name       = function.uniqueName(SPLIT_PREFIX.symbolName());
 242 
 243         final Block newBlock = new Block(token, finish, statements);
 244 
 245         return new SplitNode(name, newBlock, compiler.findUnit(weight + WeighNodes.FUNCTION_WEIGHT));
 246     }
 247 
 248     private boolean isBlockScopedVarNode(final Statement statement) {
 249         return statement instanceof VarNode && ((VarNode) statement).isBlockScoped();
 250     }
 251 
 252     @Override
 253     public boolean enterBlock(final Block block) {
 254         if (block.isCatchBlock()) {
 255             return false;
 256         }
 257 
 258         final long weight = WeighNodes.weigh(block, weightCache);
 259 
 260         if (weight < SPLIT_THRESHOLD) {
 261             weightCache.put(block, weight);
 262             return false;
 263         }
 264 
 265         return true;
 266     }
 267 
 268     @Override
 269     public Node leaveBlock(final Block block) {
 270         assert !block.isCatchBlock();
 271 
 272         Block newBlock = block;
 273 
 274         // Block was heavier than SLIT_THRESHOLD in enter, but a sub-block may have
 275         // been split already, so weigh again before splitting.
 276         long weight = WeighNodes.weigh(block, weightCache);
 277         if (weight >= SPLIT_THRESHOLD) {
 278             final FunctionNode currentFunction = lc.getCurrentFunction();
 279             newBlock = splitBlock(block, currentFunction);
 280             weight   = WeighNodes.weigh(newBlock, weightCache);
 281             lc.setFlag(currentFunction, FunctionNode.IS_SPLIT);
 282         }
 283         weightCache.put(newBlock, weight);
 284         return newBlock;
 285     }
 286 
 287     @SuppressWarnings("rawtypes")
 288     @Override
 289     public Node leaveLiteralNode(final LiteralNode literal) {
 290         final long weight = WeighNodes.weigh(literal);
 291 
 292         if (weight < SPLIT_THRESHOLD) {
 293             return literal;
 294         }
 295 
 296         final FunctionNode functionNode = lc.getCurrentFunction();
 297 
 298         lc.setFlag(functionNode, FunctionNode.IS_SPLIT);
 299 
 300         if (literal instanceof ArrayLiteralNode) {
 301             final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode) literal;
 302             final Node[]           value            = arrayLiteralNode.getValue();
 303             final int[]            postsets         = arrayLiteralNode.getPostsets();
 304             final List<Splittable.SplitRange> ranges = new ArrayList<>();
 305 
 306             long totalWeight = 0;
 307             int  lo          = 0;
 308 
 309             for (int i = 0; i < postsets.length; i++) {
 310                 final int  postset = postsets[i];
 311                 final Node element = value[postset];
 312 
 313                 final long elementWeight = WeighNodes.weigh(element);
 314                 totalWeight += WeighNodes.AASTORE_WEIGHT + elementWeight;
 315 
 316                 if (totalWeight >= SPLIT_THRESHOLD) {
 317                     final CompileUnit unit = compiler.findUnit(totalWeight - elementWeight);
 318                     ranges.add(new Splittable.SplitRange(unit, lo, i));
 319                     lo = i;
 320                     totalWeight = elementWeight;
 321                 }
 322             }
 323 
 324             if (lo != postsets.length) {
 325                 final CompileUnit unit = compiler.findUnit(totalWeight);
 326                 ranges.add(new Splittable.SplitRange(unit, lo, postsets.length));
 327             }
 328 
 329             log.info("Splitting array literal in '", functionNode.getName(), "' as its weight ", weight, " exceeds split threshold ", SPLIT_THRESHOLD);
 330 
 331             return arrayLiteralNode.setSplitRanges(lc, ranges);
 332         }
 333 
 334         return literal;
 335     }
 336 
 337     @Override
 338     public Node leaveObjectNode(final ObjectNode objectNode) {
 339         final long weight = WeighNodes.weigh(objectNode);
 340 
 341         if (weight < SPLIT_THRESHOLD) {
 342             return objectNode;
 343         }
 344 
 345         final FunctionNode functionNode = lc.getCurrentFunction();
 346         lc.setFlag(functionNode, FunctionNode.IS_SPLIT);
 347 
 348         final List<Splittable.SplitRange> ranges        = new ArrayList<>();
 349         final List<PropertyNode>          properties    = objectNode.getElements();
 350         final boolean                     isSpillObject = properties.size() > CodeGenerator.OBJECT_SPILL_THRESHOLD;
 351         long totalWeight = 0;
 352         int  lo          = 0;
 353 
 354         for (int i = 0; i < properties.size(); i++) {
 355 
 356             final PropertyNode property = properties.get(i);
 357             final boolean isConstant = LiteralNode.isConstant(property.getValue());
 358 
 359             if (!isConstant || !isSpillObject) {
 360                 final long propertyWeight = isConstant ? 0 : WeighNodes.weigh(property.getValue());
 361                 totalWeight += WeighNodes.AASTORE_WEIGHT + propertyWeight;
 362 
 363                 if (totalWeight >= SPLIT_THRESHOLD) {
 364                     final CompileUnit unit = compiler.findUnit(totalWeight - propertyWeight);
 365                     ranges.add(new Splittable.SplitRange(unit, lo, i));
 366                     lo = i;
 367                     totalWeight = propertyWeight;
 368                 }
 369             }
 370         }
 371 
 372         if (lo != properties.size()) {
 373             final CompileUnit unit = compiler.findUnit(totalWeight);
 374             ranges.add(new Splittable.SplitRange(unit, lo, properties.size()));
 375         }
 376 
 377         log.info("Splitting object node in '", functionNode.getName(), "' as its weight ", weight, " exceeds split threshold ", SPLIT_THRESHOLD);
 378 
 379         return objectNode.setSplitRanges(lc, ranges);
 380     }
 381 
 382     @Override
 383     public boolean enterFunctionNode(final FunctionNode node) {
 384         //only go into the function node for this splitter. any subfunctions are rejected
 385         return node == outermost;
 386     }
 387 }
 388